Search code examples
typescripttypescript-compiler-api

How to retrieve the complete set of types in a UnionType from a symbol using the Typescript Compiler API


I've searched high and low, and even cloned the TypeScript github to try to follow the source. I found this other SO question (TypeScript compiler API - Extract the type of union type tuple), which is a little similar to what I'm trying to do but not exactly.

I'm trying to get the type of a symbol, and if it's a UnionType, I need the complete set of types that make up the union.

If I have TS code like this:

function func(x: string | undefined) {
   if (x === undefined) {
     ...
   } 
}

As I traverse the AST using the TS Compiler API, and I come to the BinaryExpression x === undefined, what I need is to get the type of the symbol x, which I foolishly thought would be straight forward. I expect this to be a UnionType, but:

const s = checker.getSymbolAtLocation(node.left); // node.left is the left side of the exp (the x)
const t = checker.getTypeOfSymbolAtLocation(s, s.valueDeclaration);
console.log(t.isUnion());

It logs false. The checker thinks string | undefined is not a UnionType?

If I change the type of x in the function declaration to: x: string | number | undefined, then I get true. Why is string | number | undefined a UnionType, but string | undefined is not?

Further, for string | undefined if I log (t as UnionType).flags, I get 4 (which is TypeFlags.String). For string | number | undefined, the .flags property returns 1048576 (which is TypeFlags.UnionType). That's technically true, but not especially helpful. There is a TypeFlags.Undefined. Its value is 32768, so why doesn't it return 4 | 8 | 32768 (32780)? Isn't that usually how bit flags work?

It seems like the type checker is insisting that | undefined doesn't count to make something a UnionType. And when you do have something that it believe qualifies as a UnionType, there appears to be no trivial way to get at it, since the .flags property just gives you back 1048576 instead of the bitwise OR of the types.

Is there any way to actually lookup the type of a symbol and, in the event that it is a UnionType, get a list of the complete set of types that make up that union?

I'll note that I have figured out a way to get the string "string | number | undefined", but getting the string and manually trying to parse it out feels pretty hacky, and it feels like there would be an "officially approved" way to do it using the TS Compiler APIs? Further, I don't think the string will work for me, b/c if any of the type are union types themselves, I would need to expand those out to get the full union.

If anyone knows how to do this, you'll be my hero.


Solution

  • Why is string | number | undefined a UnionType, but string | undefined is not?

    Enable the strictNullChecks compiler option and it will work.

    When strictNullChecks is false the compiler won't store if a type is nullable and so string | undefined gets changed to string.

    Also, once you have the type, then the union types will be on the types property:

    if (t.isUnion()) {
      const unionTypeTypes = t.types;
    }