Restricción más estricta en TypeScript que solo se permite si el caso restringido es posible
En el siguiente fragmento
interface C1 { kind: 'c1' }
interface C2 { kind: 'c2' }
interface C3<T> { kind: 'c3'; value: T }
function isC3<T, CX extends ([C3<T>] extends [CX] ? { kind: string; } : never)>(
c: CX
): c is C3<T> /* <------- PROBLEM !!! */ {
return c.kind === 'c3';
}
// valid use
type C = C1 | C2 | C3<number>;
isC3<number, C>({ kind: 'c3', value: 1 }); // true
isC3<number, C>({ kind: 'c1' }); // false
// invalid use
type D = C1 | C2;
declare var d: D;
isC3<number, D>(d); // D doesn't satisfy constraint never (as expected)
Estoy intentando hacer la función de protección de tipo isC3 de tal manera que una llamada a ella solo se compile si el tipo del argumento de tipo dado tiene el caso C3, de lo contrario me gustaría obtener un error de compilación (por favor mira los ejemplos)
Entonces todo funciona, excepto que obtengo un error en el tipo de retorno del typeguard:
A type predicate's type must be assignable to its parameter's type.
Type 'C3<T>' is not assignable to type 'CX'.
'C3<T>' is assignable to the constraint of type 'CX', but 'CX' could be instantiated with a different subtype of constraint '{ kind: string; }'.ts(2677)
¿Hay alguna manera de obtener lo que estoy buscando?
Respuesta #1
Si tienes un tipo T que sabes que es asignable a U pero el compilador no lo sabe, y necesitas usar T en un lugar donde el compilador espera algo asignable a U, puedes usar el tipo de utilidad Extract<T, U> o posiblemente Extract<U, T>, o puedes usar la intersección T & U, dependiendo de tu caso de uso:
declare function isC3<T, CX extends ([C3<T>] extends [CX] ? { kind: string; } : never)>(
c: CX
): c is Extract<CX, C3<T>> // okay
o
function isC3<T, CX extends ([C3<T>] extends [CX] ? { kind: string; } : never)>(
c: CX
): c is CX & C3<T> {
return c.kind === 'c3';
}
Ambos terminan resultando en un tipo que es efectivamente C3<T>. Extract<T, U> filtra una unión en T para quedarse solo con aquellos miembros asignables a U (así que si esperas que CX sea una unión, entonces Extract<CX, C3<T>> es razonable), mientras que T & U es más agnóstico respecto a las uniones.
Enlace de Playground al código
