Reputation: 271
How do I create a function that only accepts a const assertion
of a number. Eg:
function func(arg: number as const){} // somehow do this
func(1 as const); // valid
func(1); // error
Is this possible?
The reason I want to do this is because I want to return a tuple of length = arg
I do have a type, that create a tuple of given length:
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]
So I want to use it like this:
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;
}
Upvotes: 5
Views: 2077
Reputation: 328618
There isn't a way to require that the caller literally writes as const
as part of the parameter. You probably don't even really want that anyway; presumably you just want the function to require that arg
be of a numeric literal type instead of the number
type.
The first step toward achieving this is to give the compiler a hint that you want the type of arg
to be inferred as a numeric literal if possible... this would be more like requesting a const
assertion interpretation of the input instead of requiring it. There's no similar syntax like as const
in the call signature side, (see microsoft/TypeScript#30680 for a relevant feature request), but there are other ways to get this behavior. You could make func()
a generic function and have the type parameter T
be constrained to number
. A type constraint like T extends number
is a hint that the compiler should try to infer a numeric literal for T
instead of number
. (See microsoft/TypeScript#10676 which implements and describes this behavior).
So let's try it:
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
Here the compiler infers that T
is 1
(and not number
) in both the func(1 as const)
call, as well as the plain func(1)
call. This is probably even better than spewing errors when you call func(1)
, right?
That's great, but it still doesn't prevent number
from being passed, like this:
let someNumber: number = 1;
func(someNumber); // no error,
// function func<number>(arg: number): void
To truly prevent that, we need to get creative with our generic constraints, and employ conditional types:
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) { }
What I've done there is to have the compiler look at arg
and infer T
. It then checks that it is assignable to number extends T ? SomeNumericLiteral : number
. If T
is inferred as a numeric literal, or really any type that isn't number
or some supertype of it, then the conditional type collapses to number
, and it behaves the same as before with just T extends number
:
func(1 as const); // function func<1>(arg: 1): void
func(1); // function func<1>(arg: 1): void
On the other hand, if T
is inferred as number
or some supertype of it, then the conditional type collapses to SomeNumericLiteral
, and T extends SomeNumericLiteral
will fail the constraint, because number
or some supertype does not extend SomeNumericLiteral
by design:
let someNumber: number = 1;
func(someNumber); // error!
// ~~~~~~~~~~
// Argument of type 'number' is not assignable to parameter of type 'SomeNumericLiteral'.
Note that I defined SomeNumericLiteral
as 1 | 2 | 3
, but we just needed to use some type that number
doesn't extend to make a compiler error happen; I chose 1 | 2 | 3
since hopefully that will make the error message nicer in cases people pay close attention to it. The usual go-to thing to write is never
instead of SomeNumericLiteral
, but that makes a weird message.
So there you go... now func()
only accepts numeric literals and rejects number
.
Upvotes: 3