На протяжении более десяти лет одной из самых раздражающих проблем типизации в TypeScript была неспособность компилятора автоматически сузить тип массива после вызова стандартного метода Array.prototype.filter():
// Legacy TypeScript (версии ниже 5.5):
const numbersOrNull = [1, 2, null, 4, null];
const onlyNumbers = numbersOrNull.filter((x) => x !== null);
// ТИП ВЫВОДИЛСЯ КАК: (number | null)[]
// Компилятор не понимал, что все null были гарантированно отфильтрованы!Чтобы исключить null или undefined из результирующего массива, разработчики были вынуждены писать ручные функции с предикатами типов (Type Predicate):
// Вынужденный boilerplate до TypeScript 5.5:
const onlyNumbers = numbersOrNull.filter(
(x): x is number => x !== null
);Этот подход порождал опасный класс архитектурных дефектов — Type Guard Lie. Если программист ошибался в предикате или теле функции, компилятор слепо верил сигнатуре x is number, маскируя критические рантайм-сбои Cannot read property of undefined.
Начиная с TypeScript 5.5+, компилятор научился производить автоматический вывод предикатов типов (Inferred Type Predicates) при анализе потока управления (Control Flow Analysis / CFA).
TypeScript 5.5+ автоматически выводит сигнатуру (x: T): x is NarrowedType для обычных функций и стрелочных коллбэков, возвращающих булеву проверку типа, навсегда устраняя необходимость ручного аннотирования в filter() и защищая от человеческих ошибок Type Guard Lie.
В TypeScript 5.5+ компилятор инспектирует абстрактное синтаксическое дерево (AST) тела функции и сопоставляет входящие аргументы с возвращаемым булевым выражением.
Если функция удовлетворяет четырём строгим инвариантам:
: boolean, компилятор принудительно отключит вывод предиката и вернет примитивный логический тип;return condition или все ветки однозначно возвращают булевы результаты;typeof x === 'string', x !== null, x instanceof Date, проверка дискриминатора x.kind === 'admin');компилятор автоматически синтезирует тип возврата parameter is NarrowedType вместо тривиального boolean.
+-------------------------------------------------------------------------+
| Эволюция вывода предикатов в TypeScript 5.5+ |
+-------------------------------------------------------------------------+
| До TypeScript 5.5: |
| const isString = (x: unknown) => typeof x === 'string'; |
| • Сигнатура функции: (x: unknown) => boolean |
| • items.filter(isString) => тип остается unknown[] |
| • Требовалось: (x: unknown): x is string => ... (хрупкий boilerplate) |
+-------------------------------------------------------------------------+
│
▼ Релиз TypeScript 5.5+
+-------------------------------------------------------------------------+
| Современный TypeScript 5.5+: |
| const isString = (x: unknown) => typeof x === 'string'; |
| • Сигнатура компилятора: (x: unknown): x is string |
| • items.filter(isString) => тип автоматически сужается до string[] |
| • Нулевой бойлерплейт, математическая гарантия соответствия тела функции|
+-------------------------------------------------------------------------+Компилятор убеждается, что у функции нет принудительной аннотации возвращаемого значения : boolean.
Type Checker исследует возвращаемое логическое выражение на наличие операций сужения типа аргумента.
Анализатор Control Flow Analysis вычисляет подтип, который остается от исходного объединения при истинности выражения.
Функция неявно получает предикатную сигнатуру, прозрачно активируя правильные перегрузки в Array.prototype.filter.
Разработчик написал функцию: 'const isNumber = (val: unknown): boolean => typeof val === 'number';'. Почему вызов [1, 'two', 3].filter(isNumber) в TypeScript 5.5 возвращает тип (string | number)[], а не number[]?
Разделение успешных и отклоненных параллельных асинхронных задач: settled.filter(r => r.status === 'fulfilled'). Компилятор автоматически сужает массив до PromiseFulfilledResult<T>[], давая безопасный доступ к полю .value без ручных приведений типов as.
При выборках из GraphQL или реляционных БД с опциональными связями: users.filter(u => u.profile !== null). Результирующий список получает тип гарантированно инициализированных профилей.
Фильтрация потока доменных событий: events.filter(e => e.type === 'ORDER_CREATED'). Компилятор мгновенно распознает Discriminated Unions и сужает тип события.
Метод array.find(item => item instanceof AdminUser) в TypeScript 5.5+ автоматически возвращает тип AdminUser | undefined вместо базового User | undefined.
Разберем конвейер фильтрации разнородных событий и агрегации параллельных задач.
Исходный массив содержит объединение типов: (UserSession | null | undefined)[].
Разработчик передает функцию-фильтр без аннотации типа возврата: (s) => s != null.
Компилятор выводит сигнатуру: (s: UserSession | null | undefined): s is UserSession.
Сигнатура Array.prototype.filter выбирает перегрузку S[] и возвращает UserSession[].
export interface UserSession {
id: string;
email: string;
role: 'admin' | 'editor' | 'guest';
lastActive: Date | null;
}
/**
* 1. Фильтрация примитивов:
* Компилятор выводит: (val: string | number | null): val is string
*/
export const isString = (val: string | number | null) => typeof val === 'string';
const mixedValues: (string | number | null)[] = ['alpha', 42, null, 'beta'];
// В TypeScript 5.5+ stringsOnly имеет строгий тип string[]!
export const stringsOnly = mixedValues.filter(isString);
/**
* 2. Инлайн-фильтрация с оператором неравенства:
* Сигнатура автоматически выводится как: (s: UserSession | null | undefined): s is UserSession
*/
export function getActiveSessions(
sessions: readonly (UserSession | null | undefined)[]
): UserSession[] {
// Тип автоматически сужается до UserSession[]
return sessions.filter((s) => s !== null && s !== undefined);
}
/**
* 3. Сужение размеченных объединений (Discriminated Unions)
*/
export type AppEvent =
| { type: 'AUTH_SUCCESS'; userId: string; token: string }
| { type: 'AUTH_FAILURE'; error: string }
| { type: 'HEARTBEAT'; timestamp: number };
/**
* Сигнатура автоматически инферится как:
* (event: AppEvent): event is { type: 'AUTH_SUCCESS'; userId: string; token: string }
*/
export const isAuthSuccessEvent = (event: AppEvent) => event.type === 'AUTH_SUCCESS';
export function extractAuthTokens(events: readonly AppEvent[]): string[] {
// Компилятор знает, что у отфильтрованных событий гарантированно есть поле .token!
return events
.filter(isAuthSuccessEvent)
.map((e) => e.token);
}Можно ли автоматически вывести предикат типа для асинхронной функции 'async function checkAccess(user: User): Promise<boolean>'?
Демонстрация промышленного агрегатора асинхронных задач, использующего автоматический вывод предикатов для мгновенного и безопасного разделения результатов Promise.allSettled.
export interface TaskResult<T> {
successful: T[];
failedErrors: Error[];
}
/**
* Агрегатор параллельных задач с автоматическим сужением типов PromiseSettledResult
*/
export async function runParallelTasks<T>(
tasks: readonly Promise<T>[]
): Promise<TaskResult<T>> {
const settled = await Promise.allSettled(tasks);
// Предикат (r): r is PromiseFulfilledResult<T> выводится АВТОМАТИЧЕСКИ!
const fulfilled = settled.filter(
(r) => r.status === 'fulfilled'
);
// Предикат (r): r is PromiseRejectedResult выводится АВТОМАТИЧЕСКИ!
const rejected = settled.filter(
(r) => r.status === 'rejected'
);
return {
successful: fulfilled.map((r) => r.value),
failedErrors: rejected.map((r) =>
r.reason instanceof Error ? r.reason : new Error(String(r.reason))
),
};
}Самая распространенная ошибка разработчиков, привыкших явно типизировать возвращаемые значения функций:
Если написать const isValid = (x: unknown): boolean => x !== null, компилятор принудительно отключит вывод предиката. В результате в array.filter(isValid) сужение типов работать не будет!
// ❌ АНТИПАТТЕРН: Явный : boolean подавляет вывод предиката
const isNotNull = (x: string | null): boolean => x !== null;
const list = ['a', null, 'b'].filter(isNotNull); // Тип: (string | null)[]
// ✅ ПРАВИЛЬНО: Отсутствие аннотации активирует автоматический предикат x is string
const isNotNull = (x: string | null) => x !== null;
const list = ['a', null, 'b'].filter(isNotNull); // Тип: string[]Компилятор TypeScript 5.5 синтезирует предикат только тогда, когда логическая цепочка между аргументом и результатом прозрачна. Если внутри функции вводятся промежуточные переменные, происходят мутации или вызываются сторонние непрозрачные утилиты, компилятор откатится к возврату boolean. В таких случаях требуется явный предикат x is Type.
Если проверить return Boolean(user.details?.profile?.verified), компилятор поймет, что поле verified истинно, но не сможет гарантированно сузить корневой объект user, если user мог быть null. Всегда сужайте родительский контекст перед проверкой дочерних свойств.
Появление Inferred Type Predicates в TypeScript 5.5+ — одно из самых долгожданных улучшений языка за последние годы. Оно делает работу со стандартным методом Array.prototype.filter интуитивной и типобезопасной «из коробки», исключает написание хрупкого бойлерплейта x is Type и математически защищает кодовую базу от фатальных ошибок рассинхронизации типов (Type Guard Lie).