Durante más de una década, uno de los puntos de fricción más notorios en TypeScript fue la incapacidad del compilador para estrechar automáticamente el tipo de un array tras invocar Array.prototype.filter():
// Legacy TypeScript (anterior a 5.5):
const numbersOrNull = [1, 2, null, 4, null];
const onlyNumbers = numbersOrNull.filter((x) => x !== null);
// EL TIPO RESULTANTE PERMANECÍA: (number | null)[]
// ¡El compilador no comprendía que los elementos null habían sido filtrados!Para depurar valores null o undefined de un array resultante, los desarrolladores debían recurrir obligatoriamente a predicados de tipo manuales (Type Predicate):
// Código repetitivo forzado antes de TypeScript 5.5:
const onlyNumbers = numbersOrNull.filter(
(x): x is number => x !== null
);Este esquema propiciaba una categoría insidiosa de fallos arquitectónicos: Type Guard Lie. Si el programador cometía un descuido en la condición o en la firma, el compilador aceptaba ciegamente la aseveración x is number, enmascarando fallos críticos como Cannot read property of undefined.
A partir de TypeScript 5.5+, el compilador incorpora la inferencia automática de predicados de tipo (Inferred Type Predicates) durante el análisis de flujo de control (Control Flow Analysis / CFA).
TypeScript 5.5+ sintetiza automáticamente firmas (x: T): x is NarrowedType para funciones estándar y flechas que retornan comprobaciones de tipo booleanas, suprimiendo la necesidad de anotaciones manuales en filter() y blindando el código contra anomalías Type Guard Lie.
En TypeScript 5.5+, el compilador examina el Árbol de Sintaxis Abstracta (AST) del cuerpo de la función y enlaza los parámetros entrantes con la expresión lógica de retorno.
Si la función satisface cuatro invariantes estrictos:
: boolean obliga al compilador a respetar el tipo booleano básico, desactivando la síntesis del predicado;return condition o todas las ramas devuelven valores booleanos de forma inequívoca;typeof x === 'string', x !== null, x instanceof Date, discriminación x.kind === 'admin');el compilador sintetiza el tipo de retorno parameter is NarrowedType en lugar del genérico boolean.
+-------------------------------------------------------------------------+
| Evolución de la inferencia de predicados en TS 5.5+ |
+-------------------------------------------------------------------------+
| Previo a TypeScript 5.5: |
| const isString = (x: unknown) => typeof x === 'string'; |
| • Firma deducida: (x: unknown) => boolean |
| • items.filter(isString) => el tipo persiste como unknown[] |
| • Exigía: (x: unknown): x is string => ... (código frágil repetitivo) |
+-------------------------------------------------------------------------+
│
▼ Lanzamiento TypeScript 5.5+
+-------------------------------------------------------------------------+
| TypeScript 5.5+ moderno: |
| const isString = (x: unknown) => typeof x === 'string'; |
| • Firma del compilador: (x: unknown): x is string |
| • items.filter(isString) => el array se estrecha a string[] |
| • Cero código redundante, verificación matemática del cuerpo funcional |
+-------------------------------------------------------------------------+El compilador corrobora que la función no presente una anotación estricta de retorno como : boolean.
El Type Checker analiza la expresión booleana evaluando si ejecuta estrechamientos sobre los parámetros.
Control Flow Analysis determina el subtipo residual de la unión cuando la expresión evalúa como verdadera.
La función adquiere implícitamente la firma de predicado, activando las sobrecargas correctas en Array.prototype.filter.
Un desarrollador redacta: 'const isNumber = (val: unknown): boolean => typeof val === 'number';'. ¿Por qué la llamada [1, 'two', 3].filter(isNumber) en TypeScript 5.5 produce (string | number)[] en vez de number[]?
Discriminación entre tareas concurrentes exitosas y rechazadas: settled.filter(r => r.status === 'fulfilled'). El compilador estrecha automáticamente el array a PromiseFulfilledResult<T>[], posibilitando el acceso seguro a .value sin aserciones manuales as.
Al procesar entidades de GraphQL o bases de datos relacionales con relaciones opcionales: users.filter(u => u.profile !== null). El array resultante se tipa garantizando perfiles no nulos.
Filtrado de flujos de eventos de dominio: events.filter(e => e.type === 'ORDER_CREATED'). El compilador discrimina la Discriminated Union hacia la estructura exacta del evento.
Ejecutar array.find(item => item instanceof AdminUser) en TypeScript 5.5+ retorna automáticamente AdminUser | undefined en lugar de User | undefined.
Analizaremos una tubería tipada para la segregación de eventos dispares y la agregación de tareas paralelas.
El array de origen alberga una unión: (UserSession | null | undefined)[].
El desarrollador suministra una condición sin tipo de retorno: (s) => s != null.
El compilador deduce la firma: (s: UserSession | null | undefined): s is UserSession.
Array.prototype.filter selecciona la sobrecarga S[], retornando UserSession[].
export interface UserSession {
id: string;
email: string;
role: 'admin' | 'editor' | 'guest';
lastActive: Date | null;
}
/**
* 1. Filtrado de primitivas:
* El compilador infiere: (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'];
// ¡En TypeScript 5.5+, stringsOnly queda estrictamente tipado como string[]!
export const stringsOnly = mixedValues.filter(isString);
/**
* 2. Filtrado en línea con desigualdad:
* La firma se deduce automáticamente como: (s: UserSession | null | undefined): s is UserSession
*/
export function getActiveSessions(
sessions: readonly (UserSession | null | undefined)[]
): UserSession[] {
// Se estrecha de forma nativa a UserSession[]
return sessions.filter((s) => s !== null && s !== undefined);
}
/**
* 3. Estrechamiento de uniones discriminadas (Discriminated Unions)
*/
export type AppEvent =
| { type: 'AUTH_SUCCESS'; userId: string; token: string }
| { type: 'AUTH_FAILURE'; error: string }
| { type: 'HEARTBEAT'; timestamp: number };
/**
* Firma inferida automáticamente:
* (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[] {
// ¡El compilador verifica que los eventos resultantes poseen la propiedad .token!
return events
.filter(isAuthSuccessEvent)
.map((e) => e.token);
}¿Es posible que TypeScript deduzca automáticamente un predicado de tipo para una función asíncrona: 'async function checkAccess(user: User): Promise<boolean>'?
Agregador de tareas asíncronas que utiliza predicados de tipo inferidos para segregar resultados de Promise.allSettled de forma segura y sin aserciones as.
export interface TaskResult<T> {
successful: T[];
failedErrors: Error[];
}
/**
* Agregador de tareas en paralelo con estrechamiento automático de PromiseSettledResult
*/
export async function runParallelTasks<T>(
tasks: readonly Promise<T>[]
): Promise<TaskResult<T>> {
const settled = await Promise.allSettled(tasks);
// ¡El predicado (r): r is PromiseFulfilledResult<T> se infiere AUTOMÁTICAMENTE!
const fulfilled = settled.filter(
(r) => r.status === 'fulfilled'
);
// ¡El predicado (r): r is PromiseRejectedResult se infiere AUTOMÁTICAMENTE!
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))
),
};
}El descuido más común entre programadores habituados a anotar exhaustivamente los tipos de retorno:
Si se escribe const isValid = (x: unknown): boolean => x !== null, el compilador anula la inferencia del predicado. En consecuencia, array.filter(isValid) no estrechará los tipos resultantes.
// ❌ ANTIPATRÓN: La anotación : boolean anula el predicado
const isNotNull = (x: string | null): boolean => x !== null;
const list = ['a', null, 'b'].filter(isNotNull); // Tipo: (string | null)[]
// ✅ CORRECTO: Omitir la anotación habilita el predicado automático x is string
const isNotNull = (x: string | null) => x !== null;
const list = ['a', null, 'b'].filter(isNotNull); // Tipo: string[]TypeScript 5.5 infiere predicados únicamente cuando la correlación entre parámetro y resultado es transparente. Si se incorporan variables temporales, se mutan parámetros o se invocan utilidades opacas, el compilador recae en boolean. En tales supuestos se requiere un predicado manual explícito x is Type.
Si se evalúa return Boolean(user.details?.profile?.verified), el compilador deduce que verified es verdadero, pero no puede estrechar de forma segura el objeto raíz user si este podía ser null. Estreche siempre los niveles superiores antes de evaluar propiedades hijas.
La integración de Inferred Type Predicates en TypeScript 5.5+ constituye una de las evoluciones más significativas del lenguaje. Otorga a Array.prototype.filter un comportamiento intuitivo y seguro por defecto, erradica el código repetitivo x is Type y resguarda la base de código frente a incoherencias críticas de tipos (Type Guard Lie).