Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TypeScript: Is there a way to typecheck function arity?

Tags:

typescript

Given the following code:

type Function0<T> = () => T;
type Function1<T, A1> = (arg1: A1) => T;
type Function2<T, A1, A2> = (arg1: A1, arg2: A2) => T;

const foo: Function1<string, any> = () => "hi there";

I'd expect to get some sort of error, because I'm trying to assert some 0-argument function is a type that takes one argument.

However, the following compiles perfectly fine. Is there some way to type-check that these arities exactly match?

like image 447
bioball Avatar asked Dec 14 '22 15:12

bioball


2 Answers

By default typescript assumes that a function with fewer parameters can be assign to where a the function will be called with more parameters, as the extra arguments will get ignored and no harm will come of it. A reasonable assumption:

const foo: Function1<string, any> = () => "hi there";
foo("Ignored, but why would that be a problem ?")

That being said, we can in some circumstances force the passed in function to have the same number of parameters as the expected number of parameters. This scenario involves passing the function to another function and using some conditional types to force an error if there are too few arguments:

type IsArg1Valid<T extends (...a: any) => any, E> = Parameters<T>['length'] extends 1 ? {} : E ;
function withFoo<T extends (arg: string) => any>(foo:  T & IsArg1Valid<T, "You need to pass in a function with a first argument">){

}
withFoo(()=> {}) //Type '() => void' is not assignable to type '"You need to pass in a function with a first argument"'.
withFoo(a=> console.log(a)) //ok
withFoo((a, b)=> console.log(a)) 

Play

Note You should think long and hard if passing in a function with fewer parameters is truly an error, it should be harmless to do so in all circumstances at runtime. The only argument for it might be that the caller might overlook useful passed in parameters, but this might not justify forcing everyone to specify all paramters all the time

like image 125
Titian Cernicova-Dragomir Avatar answered Feb 11 '23 14:02

Titian Cernicova-Dragomir


This

const foo: Function1<string, any> = () => "hi there";

is no different from a function that declares, but does not use its arguments:

const foo_anyway: Function1<string, any> = (arg1: any) => "hi there";

In Javascript, you will get no error if you call foo_anyway without arguments.

So it does not make sense to report errors about function values that are actually compatible with declared type.

On the other hand, if it needs more arguments, it becomes incompatible and the error is reported if you turn on --strictFunctionTypes (compatibility rules for function types in TypeScript are even less strict by default, see function parameter bivariance)

const foo_error: Function1<string, any> = (arg1: any, arg2: string) => "hi there";

//Type '(arg1: any, arg2: string) => string' is not assignable 
// to type 'Function1<string, any>'.
like image 27
artem Avatar answered Feb 11 '23 15:02

artem