Search code examples
typescriptenumstype-inferencemapped-types

Typescript type inference with mapped type and enums in a function why not works?


I can't figure out, why doesn't work the type inference here (see below in the code).

enum Vehicle {
  Car,
  Bus,
  Plane,
}

interface CarParams {
  carId: string;
}

interface BusParams {
  busId: string;
}

interface PlaneParams {
  planeId: string;
}

type Params = {
  [Vehicle.Bus]: BusParams;
  [Vehicle.Car]: CarParams;
  [Vehicle.Plane]: PlaneParams;
};

function showDriver<T extends Vehicle>(vehicle: T, params: Params[T] ): void {
// ...
  if (vehicle === Vehicle.Bus) {
    params.busId //<---- Property 'busId' does not exist on type 'CarParams | BusParams | PlaneParams'.
    // Type inference doesn't work here!
  }
}

showDriver(Vehicle.Bus, { busId: '' }) // <--- type inference works here!

Solution

  • There is an in operator narrowing available, and we can take advantage of that to identify a particular member type in the union.

    enum Vehicle {
      Car,
      Bus,
      Plane,
    }
    
    interface CarParams {
      carId: string;
    }
    
    interface BusParams {
      busId: string;
    }
    
    interface PlaneParams {
      planeId: string;
    }
    
    type Params = {
      [Vehicle.Bus]: BusParams;
      [Vehicle.Car]: CarParams;
      [Vehicle.Plane]: PlaneParams;
    };
    
    function showDriver<T extends Vehicle>(vehicle: T, params: Params[T]): void {
      // ...
      if ("busId" in params) {
        console.log(params.busId);
      }
      if ("carId" in params) {
        console.log(params.carId);
      }
      if ("planeId" in params) {
        console.log(params.planeId);
      }
    }
    
    showDriver(Vehicle.Bus, { busId: 'bus123' });
    showDriver(Vehicle.Car, { carId: 'car123' });
    showDriver(Vehicle.Plane, { planeId: 'plane123' });
    

    Illustration

    "use strict";
    var Vehicle;
    (function(Vehicle) {
      Vehicle[Vehicle["Car"] = 0] = "Car";
      Vehicle[Vehicle["Bus"] = 1] = "Bus";
      Vehicle[Vehicle["Plane"] = 2] = "Plane";
    })(Vehicle || (Vehicle = {}));
    
    function showDriver(vehicle, params) {
      // ...
      if ("busId" in params) {
        console.log(params.busId);
      }
      if ("carId" in params) {
        console.log(params.carId);
      }
      if ("planeId" in params) {
        console.log(params.planeId);
      }
    }
    
    showDriver(Vehicle.Bus, {
      busId: 'bus123'
    });
    
    showDriver(Vehicle.Car, {
      carId: 'car123'
    });
    
    showDriver(Vehicle.Plane, {
      planeId: 'plane123'
    });


    WYSIWYG => WHAT YOU SHOW IS WHAT YOU GET