typescripttypescript-genericsunion-typesconditional-types

Why is this conditional based on a non-distributive union type not working?


I have an interface and want to determine if any of the values are optional. To do that, I thought I'd create a union out of all the values and then check if it extends undefined. When it didn't work, I read about distributive conditionals and thought surely that would be the solution.

Here's the code:

export interface SomeInterface {
  someBool: boolean
  someOptionalNum?: number
  someOptionalText?: string
}

const test: [SomeInterface[keyof SomeInterface]] extends [undefined] ? boolean : string = true

According to this part of the typescript guide, I would expect this code to work. But I can't get test to ever be a boolean, it remains stuck on string.

See Typescript Playground


Solution

  • Our understanding of the way extends works is causing the issue here.

    Conditional types take a form that looks a little like conditional expressions (condition ? trueExpression : falseExpression) in JavaScript:

      SomeType extends OtherType ? TrueType : FalseType;
    

    When the type on the left of the extends is assignable to the one on the right, then you’ll get the type in the first branch (the “true” branch); otherwise you’ll get the type in the latter branch (the “false” branch).

    Take a look at this code for example:

    type X = "A" | "B";
    type A = "A";
    type Y = A extends X ? true : false;
    

    Y would be true above.

    In the question, [SomeInterface[keyof SomeInterface]] is not assignable to [undefined]. In fact, [undefined] is assignable to [SomeInterface[keyof SomeInterface]].

    So you have the conditions the other way round. You can change your code like:

    export interface SomeInterface {
      someBool: boolean
      someOptionalNum?: number
      someOptionalText?: string
    }
    
    const test: [undefined] extends [SomeInterface[keyof SomeInterface]]  ? boolean : string = true
    

    and it should work.

    Playground Link