tipos - El parámetro de la función de Typescript debe ser una `const assertion` de un número
¿Cómo creo una función que solo acepte una const assertion de un número? Por ejemplo:
function func(arg: number as const){} // somehow do this
func(1 as const); // valid
func(1); // error
¿Es esto posible?
La razón por la que quiero hacer esto es porque quiero devolver una tupla de longitud=arg
De hecho, tengo un tipo que crea una tupla de la longitud dada:
type TupleFromCount<
T,
N extends number,
S extends T[] = [],
> = S['length'] extends N ? S : TupleFromCount<T, N, [T, ...S]>;
type temp = TupleFromCount<string, 5>;
// ^ type temp = [string, string, string, string, string]
Así que quiero usarlo de esta manera:
function getRecords<T extends number = 10, K>(
page: number,
itemsPerPage: T,
): TupleFromCount<K, T> {
const arr: TupleFromCount<string, T> = new Array<K>(itemsPerPage);
return arr;
}
Respuesta #1
No hay una manera de requerir que el llamador escriba literalmente as const como parte del parámetro. Probablemente ni siquiera quieras eso de todas formas; presumiblemente solo quieres que la función requiera que arg sea de un tipo literal numérico en lugar del tipo number.
El primer paso para lograr esto es darle al compilador una pista de que deseas que el tipo de arg se infiera como un literal numérico si es posible... esto sería más como solicitar una interpretación de const de la entrada en lugar de exigirla. No hay una sintaxis similar a as const en el lado de la firma de la llamada (ver microsoft/TypeScript#30680 para una solicitud de función relevante), pero hay otras formas de obtener este comportamiento. Podrías hacer que func() sea una función genérica y que el parámetro de tipo T esté restringido a number. Una restricción de tipo como T extends number es una pista de que el compilador debería intentar inferir un literal numérico para T en lugar de number. (Ver microsoft/TypeScript#10676 que implementa y describe este comportamiento).
Así que vamos a intentarlo:
function func<T extends number>(arg: T) { }
func(1 as const); // function func<1>(arg: 1): void
func(1); // no error, but function func<1>(arg: 1): void
Aquí el compilador infiere que T es 1 (y no number) tanto en la llamada func(1 as const), como en la llamada simple func(1). Esto probablemente es incluso mejor que generar errores cuando llamas a func(1), ¿verdad?
Eso es genial, pero aún no evita que se pase number, así:
let someNumber: number = 1;
func(someNumber); // no error,
// function func<number>(arg: number): void
Para prevenir eso realmente, necesitamos ser creativos con nuestras restricciones genéricas y emplear tipos condicionales:
type SomeNumericLiteral
= 1 | 2 | 3; // just a dummy numeric literal union to make the error nice
function func<T extends (number extends T ? SomeNumericLiteral : number)>(
arg: T) { }
Lo que he hecho allí es hacer que el compilador mire arg e infiera T. Luego verifica que sea asignable a number extends T ? SomeNumericLiteral : number. Si T se infiere como un literal numérico, o realmente cualquier tipo que no sea number o algún supertipo de este, entonces el tipo condicional se colapsa a number, y se comporta igual que antes con solo T extends number:
func(1 as const); // function func<1>(arg: 1): void
func(1); // function func<1>(arg: 1): void
Por otro lado, si T se infiere como number o algún supertipo de este, entonces el tipo condicional se colapsa a SomeNumericLiteral, y T extends SomeNumericLiteral no cumplirá la restricción, porque number o algún supertipo no extiende SomeNumericLiteral por diseño:
let someNumber: number = 1;
func(someNumber); // error!
// ~~~~~~~~~~
// Argument of type 'number' is not assignable to parameter of type 'SomeNumericLiteral'.
Ten en cuenta que definí SomeNumericLiteral como 1 | 2 | 3, pero solo necesitábamos usar algún tipo al que number no se extienda para que ocurriera un error de compilación; elegí 1 | 2 | 3 con la esperanza de que eso haga que el mensaje de error sea más claro en los casos en que las personas presten mucha atención a él. Lo habitual es escribir never en lugar de SomeNumericLiteral, pero eso genera un mensaje extraño.
Así que ahí lo tienes... ahora func() solo acepta literales numéricos y rechaza number.
Enlace de Playground al código
