Búsqueda de sitios web

typescript - ¿Por qué un tipo de cadena/número marcado hecho DeepWriteable ya no extiende de string/número?


Tengo un tipo DeepWriteable y un tipo de número marcado PositiveNumber. PositiveNumber extiende number, pero DeepWriteable<PositiveNumber> no lo hace. La misma historia para un tipo de cadena marcado. ¿Qué está pasando aquí?

type DeepWriteable<T> = { -readonly [P in keyof T]: DeepWriteable<T[P]> };
type PositiveNumber = number & { __brand: 'PositiveNumber' };
type NonEmptyString = string & { __brand: 'NonEmptyString' };

type test1 = PositiveNumber extends number ? true : false;
// TRUE
type test2 = DeepWriteable<PositiveNumber> extends number ? true : false;
// FALSE
type test3 = NonEmptyString extends string ? true : false;
// TRUE
type test4 = DeepWriteable<NonEmptyString> extends string ? true : false;
// FALSE

(Por cierto, solo estoy preguntando sobre los tipos con marca porque los number y string normales no parecen tener este comportamiento)

type test5 = DeepWriteable<number> extends number ? true : false;
// TRUE
type test6 = DeepWriteable<string> extends string ? true : false;
// TRUE

He recurrido simplemente a agregar un chequeo a DeepWriteable para no mapear sobre numbers y strings, lo cual funciona bien, pero tengo curiosidad por qué es necesario.

¿Es porque la transformación -readonly hace que este sea un tipo mapeado no homomórfico? Creo que tal vez puedo ver por qué esto sucede con string, porque length es readonly. ¿Hay una propiedad readonly similar en number?

Respuesta #1

Cualquier tipo mapeado (incluso los homomórficos) sobre un primitivo con marca (un tipo primitivo que ha sido intersectado con un tipo similar a un objeto) terminará destruyendo el tipo primitivo.

Los tipos mapeados homomórficos en primitivos regulares, no marcados, fueron tratados de manera especial en microsoft/TypeScript#12447 para devolver el primitivo sin alteración.

Pero para los primitivos con marca obtienes el mapeo sobre todas las propiedades y propiedades aparentes del tipo de entrada, lo que significa que DeepWriteable<PositiveNumber> te dará un resultado con todas las propiedades de la interfaz Number así como una propiedad __brand.

Como se mencionó en este comentario sobre el problema relacionado microsoft/TypeScript#35992:

Marcar un primitivo con un tipo de objeto es una empresa bajo su propio riesgo. Básicamente, puedes hacer esto mientras el tipo nunca pase por una transformación de simplificación... Dicho esto, estoy describiendo el comportamiento actual, no afirmando ninguna promesa sobre el comportamiento futuro. ... Usamos algo de marca internamente, pero no reportamos errores contra nosotros mismos cuando encontramos rarezas 😉

Así que parece que la recomendación es evitar hacer demasiada manipulación de tipos en primitivas con marcas, o al menos probar dichas manipulaciones a fondo y ajustarlas para lidiar con cualquier complicación inesperada que surja cuando los tipos no se combinan como uno podría esperar.