简体   繁体   中英

Typescript generics: infer type from the type of function arguments?

I have a method that has 2 arguments and I want it to infer a type from the 1st argument.

For example, in the following code, I want the type T of the function create_C<T> to be inferred from the firstArgument so that the return type of create_C function would be C<type inferred from firstArgument>

interface C<T> { 
    firstArgument: A<T>;
    secondArgument: (obj: any) => T
}

export interface A<T> {
    type: T;
}

function create_C<T>(
    firstArgument: A<T>,
    secondArgument: (obj: any) => T
): C<T> {
    return {
        firstArgument,
        secondArgument
    }
}

However, in the following implementation, the type of const c is being inferred as C<{ prop2: number }> . But I am expecting it to be inferred as C<B> and I am expecting the compiler to throw an error saying that the return type of the secondArgument is not of type B

interface B { 
    prop1: string;
    prop2: number
}

export class B_Component implements A<B> {
    type: B = {
        prop1: "",
        prop2: 1
    };
}

const c = create_C(
    new B_Component(),
    () => ({ prop2: 2 })
)

How can I make sure for the compiler to throw an error saying that the return type of the secondArgument is not of type B ?

Here is a Stackblitz editor link: https://stackblitz.com/edit/qqddsn

In your function signature

declare function create_C<T>(a1: A<T>, a2: (obj: any) => T): C<T>;

there are two inference sites for T (an "inference site" means "someplace the compiler can use to try to infer a type for a type parameter"). One site is from the type property of the first argument a1 , and the other site is the return type of the second argument a2 . The compiler looks at a call like

create_C(new B_Component(), () => ({ prop2: 2 });

and tries to infer T from both sites. In this case, there is a match: both (new B_Component()).type and {prop2: 2} are assignable to {prop2: number} . So there's no error, and you get C<{prop2: number> coming out. In another situation, this might be exactly the behavior you want from the compiler.


Instead, you want to see the compiler use just a1 to infer T , and to just verify that a2 matches it. That is, you want the T in (obj: any) => T to be a non-inferential type parameter (see microsoft/TypeScript#14829) . Unfortunately, there is no "official" support for this. But fortunately, there are workaround techniques which can often be used to get this behavior.

Here's one such technique: if you change a type parameter in an inference site from T to T & {} , it lowers the site's priority . So the compiler will tend to infer T from other inference sites first and only come back to the T & {} one if it fails to infer from other places. And the type T & {} is very similar to T (if T is an object type then it's basically the same) so it doesn't change the semantics much. Let's try it:

declare function create_C_better<T>(a: A<T>, b: (obj: any) => T & {}): C<T>;

Here goes:

const c2 = create_C_better(
    new B_Component(),
    () => ({ prop2: 2 }) // error!
    //    ~~~~~~~~~~~~~~ <-- prop1 is missing
)

const c3 = create_C_better(
    new B_Component(),
    () => ({ prop1: "all right", prop2: 2 })
); // C<B>

There, you get the error you wanted when prop1 is missing, and when you fix it, you get an output of type C<B> as desired.


Okay, hope that helps; good luck!

Link to code

It is due to the secondArgument: (obj: any) => T . If you apply above definition to () => ({ prop2: 2 }) Type of T is { prop2: number } . You may change it to something else to get the desired result. For example.

    interface C<T> {
      firstArgument: A<T>;
      secondArgument: (obj: any) => any;
    }

    export interface A<T> {
      type: T;
    }

    declare function create_C<T>(
      firstArgument: A<T>,
      secondArgument: (obj: any) => any
    ): C<T>;

    interface B {
      prop1: string;
      prop2: number;
    }

    export class B_Component implements A<B> {
      type: B;
      configuration: B = {
        prop1: "",
        prop2: 1
      };
    }
    const b = new B_Component();
    export const c = create_C(b, () => ({ prop2: 2 }));

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM