title: TypeScript: generically infer union type member based on a string literal property
body: <p>TypeScript (v3.2.2) allows me to define a union of interfaces, each with a unique string literal property which can be used as a type guard, e.g.</p>
  
  <pre class="lang-ts prettyprint-override"><code>type Device = Laptop | Desktop | Phone;
  
  interface Laptop {
    type: 'Laptop';
    countDriveBays: number;
    hasTouchScreen: boolean;
  }
  
  interface Desktop {
    type: 'Desktop';
    countDriveBays: number;
  }
  
  interface Phone {
    type: 'Phone';
    hasTouchScreen: boolean;
  }
  
  function printInfo(device: Device) {
    if (device.type === 'Laptop') {
      // device: Laptop
      console.log(
        `A laptop with ${device.countDriveBays} drive bays and ${
          device.hasTouchScreen ? 'a' : 'no'
        } touchscreen.`,
      );
    } else if (device.type === 'Desktop') {
      // device: Desktop
      console.log(`A desktop with ${device.countDriveBays} drive bays.`);
    } else {
      // device: Phone
      console.log(`A phone with ${device.hasTouchScreen ? 'a' : 'no'} touchscreen.`);
    }
  }
  </code></pre>
  
  <p>I want to write a function <code>isDeviceType</code> in a generic way:</p>
  
  <pre class="lang-ts prettyprint-override"><code>const isDeviceType = &lt;T extends Device['type']&gt;(type: T) =&gt; {
    return (device: Device): device is DeviceOf&lt;T&gt; =&gt; device.type === type;
  }
  
  // e.g.
  const isPhone = isDeviceType('Phone');
  isPhone({ type: 'Phone', hasTouchScreen: true }); // true
  </code></pre>
  
  <p>However, the way I have defined the <code>DeviceOf</code> type is pretty verbose since it lists every single type within the union:</p>
  
  <pre class="lang-ts prettyprint-override"><code>type DeviceOf&lt;Type extends Device['type']&gt; =
    Type extends Laptop['type'] ? Laptop :
    Type extends Desktop['type'] ? Desktop :
    Type extends Phone['type'] ? Phone :
    never;
  </code></pre>
  
  <p><strong>Is there a more concise way to define <code>DeviceOf</code>?</strong> I have tried these:</p>
  
  <pre class="lang-ts prettyprint-override"><code>type DeviceOf&lt;Type extends Device['type']&gt; =
    (infer D)['type'] extends Type ? D : never;
  
  // TS2536: Type '"type"' cannot be used to index type 'D'.
  // TS1338: 'infer' declarations are only permitted in the 'extends' clause of a conditional type.
  // TS6133: 'D' is declared but its value is never read.
  </code></pre>
  
  <pre class="lang-ts prettyprint-override"><code>type DeviceOf&lt;Type extends Device['type']&gt; =
    (infer D) extends Device
      ? D['type'] extends Type
      ? D
      : never
      : never;
  
  // TS1338: 'infer' declarations are only permitted in the 'extends' clause of a conditional type.
  // TS6133: 'D' is declared but its value is never read.
  // TS2304: Cannot find name 'D'.
  </code></pre>
  
  <p>My impression is that error TS1338 is the limiting factor, and so it's impossible to define <code>DeviceOf</code> in a generic way in the current version of TypeScript.</p>