typescripttypescript-compiler-api

Typescript Infer type params with Compiler API


With this code (playground):

declare class Test<P = unknown, R = unknown> {
    test(p: P): R;
}

declare class M extends Test {
    test(q: number): boolean;
}

// these lines are not in real code. This is an example of what TS
// could infer from my code and what I would like to find using Compiler API
type Sgn<M> = M extends Test<infer P, infer R> ? [P, R] : never;
type sgn = Sgn<M>; // [number, boolean]

typescript could infer implicit type arguments of class M (P = number, R = boolean) from method test in class declaration.

I want to do the same using Compiler API. I have program.typeChecker and I'm stuck here. How could I get implicit type arguments?


Solution

  • The type Test in extends Test of M is not implicitly typed as Test<number, boolean>. The class M overwrites the declaration of test to be test(q: number): boolean. If you get the type of the ExpressionWithTypeArguemnts in extends Test of M it will be Test<unknown, unknown>.

    Getting Types from M

    If you know what the structure of Test is and you have...

    declare class M extends Test {
        test(q: number): boolean;
    }
    

    ...and you want to find the type of the parameter and the return type of test, then you can do the following:

    const mDecl = sourceFile.statements.find(child =>
        ts.isClassDeclaration(child) && child.name?.escapedText === "M"
    )! as ts.ClassDeclaration;
    const testMethod = mDecl.members.find(member =>
        ts.isMethodDeclaration(member)
        && ts.isIdentifier(member.name)
        && member.name.escapedText === "test"
    )! as ts.MethodDeclaration;
    
    // number type
    typeChecker.getTypeAtLocation(testMethod.parameters[0]);
    // boolean type
    typeChecker.getReturnTypeOfSignature(typeChecker.getSignatureFromDeclaration(testMethod));
    

    Given Test, finding P and R in M

    If you have Test and want to find P and R in M and you don't know what the type of Test might look like, then you cannot use the previously described method.

    To do this, you will have to traverse the type of Test manually and then traverse M in the same way and see what type the type parameters in Test are used in M. Doing this is kind of complicated and would be too much work/code to post here.

    Basically, there's no easy way to build up types and then do comparisons with the compiler API. There are some proposals to make this easier (ex. Type Builder API Proposal and Type Relationship API Proposal).

    That said, one way around it is to create a dummy file via ts.createSourceFile and write some code in there (ex. your Sgn and sgn declarations) then use the type checker on that file to resolve the types.