Search code examples
typescripttypingunion-types

TypeScript Exclude<UnionOfTypes, Interface> is type "never"


Why in the following code is Exclude<A,B> resolving to the never type? Can't the typescript compiler know (through static analysis) that A and B extend Parent and thus Exclude<Choices, Parent> should resolve to type C?

interface Parent {}

interface A extends Parent {}
interface B extends Parent {}
interface C {}

type Choices = A | B | C

type Test = Exclude<Choices, Parent> // = type "never"???

const c: C = {}
const d: Test = c // Type 'C' is not assignable to type 'never'

I could hard code Parent = A | B but I'm unsure why I need to.


Solution

  • This is because TypeScript has duck typing. Specifically, since C and Parent are the same interface, C is assignable to Parent.

    Specifically, this compiles:

    const c: C = {};
    const p: Parent = c;
    

    So, although C doesn't explicitly extend Parent, TypeScript still says that C is a Parent.

    If you want this to work, just add something to Parent that C doesn't have.

    interface Parent { foo: string }
    
    interface A extends Parent {}
    interface B extends Parent {}
    interface C {}
    
    type Choices = A | B | C
    
    type Test = Exclude<Choices, Parent> // = type C
    
    const c: C = {}
    const d: Test = c // works!