user1713450
user1713450

Reputation: 1429

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.

Upvotes: 4

Views: 576

Answers (2)

Lancer.Yan
Lancer.Yan

Reputation: 896

I created a type tool as follows

// It is the opposite operation to [exclude].
type Has<T, U> = U extends T ? U : never;

Usage is as follows

    type A = {
      a : number;
      aa:number;
      aaa:number;
    }

    type B = {
      b : number;
    }

    type C = {
      c : number;
    }

    type D = {
      d : number;
    }


    // Here means the type A
    type Node_HasA = Has<{
      a : number;
    }, A|B|C|D>

Upvotes: 0

Half
Half

Reputation: 5518

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!

Upvotes: 2

Related Questions