6

Why is the following code has an error?

I'm wondering it's a bug of TypeScript compiler.

type A =
  | {
      type: 'a';
      a: string;
    }
  | {
      type: 'b';
      b: string;
    };

type X = {
  x: string;
} & A;

type XX = Pick<X, Exclude<keyof X, 'x'>> & {
  x: number;
};

const x: XX = {} as any;

if (x.type === 'a') {
  // Property 'a' does not exist on type 'XX'
  console.log(x.a);
}

You can try this code on TypeScript Playground

mtsmfm
  • 105
  • 1
  • 6

1 Answers1

6

What you are seeing is just a consequence of how union types work. Union (unless narrowed) only allow access to common properties. So when you say keyof X the only properties that will be present in the resulting string literal union are x (which is no dependent on the union) and type which is common to both union members. Pick also uses keyof behind the scenes and so has the same issue, it will be able to pick any members of the union that are common to all union members.

You can get the desired behavior, but you can't use keyof and Pick directly. You need to use the in a conditional type with a naked type parameter. Conditional types will distribute over members of a union (you can read more here about this behavior or a more concise version here) allowing us to apply keyof and Pick over each member of the union instead of the union as a whole.

type A =
  | {
      type: 'a';
      a: string;
    }
  | {
      type: 'b';
      b: string;
    };

type X = {
  x: string;
} & A;


type UnionKeys<T> = T extends any ? keyof T : never;
type UnionPick<T, K extends UnionKeys<T>> = T extends any ? Pick<T, Extract<K, keyof T>> : never 
type XX = UnionPick<X, Exclude<UnionKeys<X>, 'x'>> & {
  x: number;
};

const x: XX = {} as any;

if (x.type === 'a') {
  console.log(x.a);
}
Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357