Consider the following code:
function ensure<TModel, TValue>(accessor: { (obj: TModel): TValue; }) {
}
interface Person {
firstName: string;
lastName: string;
}
ensure((p: Person) => p.firstName); // <-- this works
ensure<Person>(p => p.firstName); // <-- this does not work
Why is the last line a syntax error?
Supplied parameters do not match any signature of call target.
Why is p inferred to be of type any
instead of Person
?
Here's a link to the code in the TypeScript playground.
Type inference represents the Java compiler's ability to look at a method invocation and its corresponding declaration to check and determine the type argument(s). The inference algorithm checks the types of the arguments and, if available, assigned type is returned.
Assigning Generic Parameters By passing in the type with the <number> code, you are explicitly letting TypeScript know that you want the generic type parameter T of the identity function to be of type number . This will enforce the number type as the argument and the return value.
TypeScript infers types of variables when there is no explicit information available in the form of type annotations. Types are inferred by TypeScript compiler when: Variables are initialized. Default values are set for parameters.
Generics allow creating 'type variables' which can be used to create classes, functions & type aliases that don't need to explicitly define the types that they use. Generics makes it easier to write reusable code.
(another edit: partial type parameter inference was scrapped/delayed and never made it into TS3.1 or any version since up to and including TS3.7. Oh well)
EDIT: an upcoming feature of TypeScript 3.1 will allow partial type argument inference (making the example you cited work), see the pull request for more details.
Original answer (applicable to TypeScript < 3.1):
The reason the first example works is because both generics are inferred by the compiler from the types of the passed in anonymous lambda function.
Unfortunately, in when consuming generic functions in TypeScript, it's all or nothing -- you have to provide either:
Note that if a type cannot be inferred it is by default assumed to be of type: Object
, e.g.:
function example<T>(a: any): T {
return a as T;
}
let test = example(123);
The variable test
in the above example, will be of type {}
.
Specifying both generic types or specifying the type of the parameter in the method are both proper ways to handle this:
ensure<Person, string>(p => p.firstName);
ensure((p: string) => p.firstName);
The error you cite is correct, in that: no signature that takes in only one generic exists for the function ensure
.
The reason for this is that you can have functions with alternative signatures that take a different number of generic type parameters:
interface Example {
ensure<TModel, TValue>(accessor: { (obj: TModel): TValue; }): TValue;
ensure<TModel>(accessor: { (obj: TModel): any; }): any;
}
interface Person {
firstName: string;
lastName: string;
}
let test: Example;
// the method 'ensure' has now 2 overloads:
// one that takes in two generics:
test.ensure<Person, string>((p: Person) => p.firstName);
// one that takes only one generic:
test.ensure<Person>(p => p.firstName);
// when not specified, TypeScript tries to infer which one to use,
// and ends up using the first one:
test.ensure((p: Person) => p.firstName);
Playground of the above.
If TypeScript did not enforce signature matching, it wouldn't know which signature it should choose.
Now to answer the other part of your question: why is p
assumed to be any
when the function is called without explicitly stating the generics:
One reason is that the compiler cannot make any assumptions as to its possible type, TModel
is unconstrained and can literally be anything, thus the type of p
is any
.
You could constrain the generic method to an interface, like this:
ensure<TModel extends Person, TValue>(accessor: { (obj: TModel): TValue; });
Now, if you call that function without specifying the type of the parameter or types of the generics, it will be correctly inferred to Person
:
ensure(p => p.firstName); // p is now Person
Hope this fully answers your question.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With