typescripttypescript-genericsnarrowing

Typescript generics for a polymorphic function


I would like to correctly type the following function:

const noCI = itOrDescribe => (isCI ? itOrDescribe.skip : itOrDescribe)

Here's my first attempt:

const noCI = <T extends Mocha.SuiteFunction | Mocha.TestFunction>(itOrDescribe: T) => (isCI ? itOrDescribe.skip : itOrDescribe)

By 'correctly', I mean that the code should correctly infer that if describe which is of type Mocha.SuiteFunction gets passed in, the return type should be Mocha.SuiteFunction | Mocha.PendingSuiteFunction, and if it gets passed in, the return type should be Mocha.TestFunction | Mocha.PendingTestFunction.

Instead, the type is always inferred as Mocha.PendingSuiteFunction | Mocha.PendingTestFunction, regardless of which type gets passed in. Why?

To put my generics question more generically, I want a function that takes either a type A or a type B, and depending on whether A or B is passed, return a type derived from either A or B.


Solution

  • When you don't like the type Typescript infers, you can always just annotate it yourself.

    And in this case that looks like: T | T['skip'].

    import Mocha, { it, describe } from 'mocha'
    
    declare const isCI: boolean
    
    const noCI = <
      T extends Mocha.SuiteFunction | Mocha.TestFunction
    >(itOrDescribe: T): T | T['skip'] => (
      isCI ? itOrDescribe.skip : itOrDescribe
    )
    
    const noCiIt = noCI(it)
    // Mocha.PendingSuiteFunction | Mocha.PendingTestFunction
    
    const noCiDescribe = noCI(describe)
    // Mocha.PendingSuiteFunction | Mocha.PendingTestFunction
    
    noCiDescribe('test', () => {
      noCiIt('test', () => {
        //...
      })
    })
    

    See playground