Search code examples
typescriptdiscriminated-unionunion-types

Typescript complains about discriminated union types when there is no type annotation


It seems to me that typescript does not recognise discriminated union types when provided without any type annotation. Am I missing something ? Is there some reason for that ?

export type Stuff = AType | BType

export type AType = { status: Kind.A; name: string }

export type BType = { status: Kind.B; quantity: number }

export enum Kind {
  A,
  B,
}

function PlayWithStuff(stuff: Stuff) {
  console.log('some stuff', stuff)
}

const stuff = {
  status: Kind.B,
  quantity: 2,
}

PlayWithStuff(stuff)
//            ^^^^^
// Argument of type '{ status: Kind; quantity: number; }' is not assignable to parameter of type 'Stuff'.
//   Type '{ status: Kind; quantity: number; }' is not assignable to type 'BType'.
//     Types of property 'status' are incompatible.
//       Type 'Kind' is not assignable to type 'Kind.B'.

const secondStuff: Stuff = {
  status: Kind.B,
  quantity: 2,
}

PlayWithStuff(secondStuff)
// OK, the type annotation on `secondStuff` fixes the problem

Solution

  • When initializing object literals, Typescript will infer property types, but not as constants, since they are not readonly.

    So the type of stuff will be { status: Kind, quantity: number }, because you can later change it to:

    const stuff = {
        status: Kind.B,
        quantity: 2,
    };
    
    stuff.status = Kind.A;
    

    So now it's not assignable to BType (nor AType for that matter).

    You can use as const:

    const stuff = {
        status: Kind.B,
        quantity: 2,
    } as const;
    

    Now the type is inferred as { readonly status: Kind.B, readonly quantity: 2} which is always assignable to BType.

    Or you could do what you did and just give it type annotation:

    const stuff: BType = {
        status: Kind.B,
        quantity: 2,
    };
    
    stuff.status = Kind.A; // Errors