TypeScript generics: argument type inference

Jeremy Danyow

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.

niieani

(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:

  • types of all generics of the matching function's signature, or
  • no generics, if you want the compiler to "guess" the function signature that best matches your call, while inferring the types automatically (if such inference is at all possible)

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.

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

From Dev

Type inference with Generics in Swift

From Dev

Generics and type inference for a generic factory

From Dev

Swift Generics Type Inference Extensions

From Dev

Java Generics, Type Inference, Inheritance?

From Dev

Generics and type inference for a generic factory

From Dev

TypeScript type inference issue

From Dev

TypeScript: Incorrect type inference

From Dev

Typescript union type inference

From Dev

Generic Type Inference with Class Argument

From Dev

C# 5.0 Generics: param type inference

From Dev

Java ternary operator influence on generics type inference

From Dev

Problems using generics and type inference in Java

From Dev

C# Generics type inference for comman

From Dev

C# type inference, generics and interfaces

From Dev

Java/Scala Bounded Generics and type inference mismatch

From Dev

Java6, Guava, generics, type inference

From Dev

Problems using generics and type inference in Java

From Dev

Type inference in TypeScript for curried function

From Dev

Function overloading and type inference in typescript

From Dev

Typescript: Generic class type inference

From Dev

Scala: type inference of generic and it's type argument

From Dev

Haskell function argument type inference with typeclasses

From Dev

Automatic type-argument inference in scala

From Dev

Java Bounded Generics: Type inference bug? (Method invocation, JLS 15.12.2.7)

From Dev

Why does Java generics type inference break in chained method calls?

From Dev

Defining typescript generics with type safety

From Dev

TypeScript excess field checking and type inference error

From Dev

Typescript: Type inference when using decorator

From Dev

Typescript type inference not recognizing value must be defined

Related Related

HotTag

Archive