7

Is there any way to, given a CallExpression with inferred type arguments, find what those type arguments are?

Example code:

class SomeClass {
    public someMethod<T>(arg: T): void { }
}

// What is the inferred type of T in this call?
someClass.someMethod(7);

It's easy enough to find type arguments that were explicitly assigned in the code, but I can't figure out how to find what was inferred.

function inferTypeArguments(node: ts.CallExpression, typeChecker: ts.TypeChecker) {
    node.typeArguments; // is empty
    const signature = typeChecker.getResolvedSignature(node);
    signature['typeArguments']; // is also empty

    // This returns "<number>(arg: number): void"
    // so I know that the typeChecker has the right information,
    // but I would really like a ts.Type[]
    typeChecker.signatureToString(signature, node, ts.TypeFormatFlags.WriteTypeArgumentsOfSignature)
}

4 Answers 4

2

I'll leave this question open in case someone finds a better way, but I did accomplish this by using some internal members of "Signature".

type TypeMapper = (t: ts.TypeParameter) => ts.Type;
function inferTypeArguments(node: ts.CallExpression, typeChecker: ts.TypeChecker): ts.Type[] {
  const signature = typeChecker.getResolvedSignature(node);
  const targetParams: ts.TypeParameter[] = signature['target'] && signature['target'].typeParameters;
  if (!targetParams) {
    return [];
  }
  const mapper: TypeMapper = signature['mapper'];
  return mapper
    ? targetParams.map(p => mapper(p))
    : targetParams;
}
Sign up to request clarification or add additional context in comments.

1 Comment

broken in 3.9... they changed mapper from a function to an object, it seems. :/
1

I'd been using Simon's code for a while in my transpiler... then 3.9 came along and broke it. I've done a preliminary attempt to get it working again. Unfortunately, the mapper is an "internal" concern for typescript, so this will likely change again in the future

/* @internal - from typescript 3.9 codebase*/
const enum TypeMapKind {
  Simple,
  Array,
  Function,
  Composite,
  Merged,
}

/* @internal - from typescript 3.9 codebase*/
type TypeMapper =
  | { kind: TypeMapKind.Simple, source: ts.Type, target: ts.Type }
  | { kind: TypeMapKind.Array, sources: readonly ts.Type[], targets: readonly ts.Type[] | undefined }
  | { kind: TypeMapKind.Function, func: (t: ts.Type) => ts.Type }
  | { kind: TypeMapKind.Composite | TypeMapKind.Merged, mapper1: TypeMapper, mapper2: TypeMapper };

/* basic application of the mapper - recursive for composite.*/ 
function typeMapper(mapper:TypeMapper, source: ts.Type): ts.Type {
  switch(mapper.kind){
    case TypeMapKind.Simple: 
      return mapper.target;
    case TypeMapKind.Array:
      throw Error("not implemented");
    case TypeMapKind.Function: 
      return mapper.func(source);      
    case TypeMapKind.Composite: 
    case TypeMapKind.Merged:
      return typeMapper(mapper.mapper2, source);
  }
}

function inferTypeArguments(node: ts.CallExpression, typeChecker: ts.TypeChecker): ts.Type[] {
  const signature:ts.Signature = typeChecker.getResolvedSignature(node);
  const targetParams: ts.TypeParameter[] = signature['target'] && signature['target'].typeParameters;

  if (!targetParams) {
    return [];
  }

  if(signature['mapper'] == undefined)   
    return targetParams;   

  //typescript <= 3.8
  if(typeof signature['mapper'] == "function") 
    return targetParams.map(p=>signature['mapper'](p));
  //typescript >= 3.9.... 
  return targetParams.map(p=> typeMapper(signature['mapper'] as TypeMapper, p));
} 

1 Comment

Since nobody's found a way to do this over the last 3 years without digging into internals, I'm going to accept your updated answer
0

Actually now it is '4.9.4', the latest 'getMappedType' is so complicated that I just give up implement it by myself

Comments

0

I created an issue at TypeScript's repo to propose a new API to solve this: https://github.com/microsoft/TypeScript/issues/59637

It also lists some workarounds to solve this issue.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.