Repository: TypeScript
Issue: Nested call not inferred correctly when a conditional type tries to route to the final expected type
Labels: Needs Investigation

# Bug Report

### 🔎 Search Terms

inference, type parameters, conditional type, signature resolving

### 🕗 Version & Regression Information

This is the behavior present in all 4.x versions available on the playground, including 4.5-beta.

### ⏯ Playground Link

[Slimmed down repro case](https://www.typescriptlang.org/play?ts=4.5.0-beta#code/JYOwLgpgTgZghgYwgAgKIDcLgPICMBWECYyA3gFDLJgCeADhAFzIDOYUoA5gNzkC+5cqEixEKACr0InLABFgLOLgA2EACZlKyAEQABXQA82cSAHpaDGSG3N4ylhF4Dh0eEmSTLWVCCWqNFFR6hsZmFtJYNtRQAK6O-ILhHlJWAMIA9iBsUHDCyAC8yV4gPn7qyAA+RREg8ooq6rxC4K5iyACCxMCZeITEADziGFgkEAaQIGosaJg4BERgAHyaVOHM2Vy8VAD6QwD8zEOzYE6CakTKcFAoMDEgXZnIcCwswJwgg8PgyGMTUzMjXoLRYACi0z1e7wAtiNmCCIOhDl8wABKArLdDpYBqcgo5idMDdEBAgZHEaLJrnBCXa7IW73QmPBDXEwQACyiAAFqAIP0tOJPBAWGyIGA4D9xlh-oK0plsrlvoUZXIFGUcaCtAhMjA3sxAlRqFIWAcPILhaK4FtkHwADTgh4gE0Co0isUSv7TZUlXwNHEG5B7MhPB34h0ksD9UiGhjMbQAdQAkuIABK7ACaAAVUABxVAAOW01uWAn9euDjJAoYr4cj0aYOkTKewAFVxOms7mC0XrbjmJjsU1mRBWRyENyQBAwVR9asjXq+E9PSlvD7-HaqLatDPkKZTMgAAaICv75B0KDpBhQWjIBTIdIwJ7ILVQa7EOvIAC0N9GBgYxGm+4EkSNZRmsDZJqm4iZjm+aFnwxb7loVBHkSzAQm8IAgvCxxovkyzblQu7ILgMQkGAnIoEk97UNyAHoe8J6cs8xEQFgN4gDA0DXBoYDpAeQE9PMAygVIsaNsmLZtlBHawdalRBmB8YQe2MFdvBiH+oRe4sHx+4IiMjHQCgt7UUk+4iTG4FNq2KmdnB8kWfWSkprZsl8BpmlYdsiBIHQYBicp0mqdouH4XwKLYSMAB04QolaG4ouuPZxWcFxXDcdwOk+LKQKO44QAATHyVDOgw5pur8UpLsUGRZOwCokEqy61KqvrkBqVBahxuorAa4TGocZqupaWibshDomtuRHACQADu6RQAA1tMz6vmAyg0DeD6zSgY5EItNEoPupVCsNJ63FeFFQMgajpLNICHcg55wH6BooZkg0uha7pVdUVilG1mkBh0YZCRGjkBS5QV2UWSEGlWwFg7WiniZJrlqRSo29sg-ZqIOOXslyPIFVOvWzmV86Ln9K5qklY1k+WqGLpCmGReAoUM1QXk+RAfmQ5B0F2RzpDhWzYAxVIKX+uFdO4rwQA)

[The originally reported TS playground](https://www.typescriptlang.org/play?ts=4.4.3#code/C4TwDgpgBAwghgZ2AHgCpQgD2BAdgEwSjlxABopV4lVxoscCiSQA+KAXko2z0MurBakAFBQoAfkpioALgGIhdANwjQkKAAUATgHswaCgGl2XIz0b8A1hBC6AZtymoA2kYC6cqLggA3CNqqanRQAJIEWABCIMIQaBZ8RADeUOoQ8kjaAJa4AOZQAL6mUEkyblA5lC4ARGnV7u7yAKLY2nAAxiioFClp8uZFqgVBOTja9h3QTf64wADyAEYAVhCdJTLifVCZOblDIiKjARPt0LG5eAAiWQhwCwA2EPjr4tUAAm+YSHA4APRpF1w1XkE3uCAg+yO40mlDogKauDuj2epVeHy+wB+EH+cLwwNS2gArhCRAUDmlYZBATBdLhMnBRpxKRB4YiHk8oAAfZmA663dn4IIUgBKEAQunu-nOeAAshBMcgZKhYgg5ZiEkweXgaXTgG1RmQldM8MANfxenQMnrdoURMVlXRVfK4GaiNLcAikU8ZM4VWqXQAyF7iEM5fBYJ7G2YIeThcOYaKxNBR4CsVQh23ieTuvlewUHKEnaAAQU6WVpixWnWTM1NDESUBTldWqeDWx2eXTUAA+qgmhJsyn9odZscYTKOgALHIQOZgYDluk1k2uxu15uddio4hl2kIAfB8QuREAW3S22teUaUFLC4ryxby9maZkw1J5JC7on7WnPjnd6XJVRXFSUnj9Z1DXEPtawQeBCQQXZb0XIguB0fQ0GAiV-HwcDMQoao-BNWC4HgxDdzpapWEgyg4wjfAUxQrQ9AMVBMNAnDHX9fCwzohjKLtJltw6AD93kbcj3MSobDsRxoKIuCELyJC93cA9lNwDcUA2ENBGQNCWNozBIxgig5OjBSyJEtx3CotcTU01htJfcQ3zfYINAnEAFggL8pxnf9kMVKCU1XJsH06ajWLFLCwM450mRzG48wErgopA7DcJdetNXdT0BR9LVcG-X9Z3nQK0pijjICdTFHKzKBiv8sq9yfVMgnDdp7jgbRoHsQlcHI4gEAQ3JcFa0L13C1MAAoZEQEbcDPWZ5GmvxB1rABKTh2F8XQsnwEQNvkdTNNal8RA6rqeqgPqBoAqB2h6rFGp8ILKBC7LzVSS0L2yPJCkizLV3dHV6UZVLcVwXMBUNVhZvEdpaXsLJcjE7SEB-CATzgA9xJDQjowPMzgC7FzSe+6qicyrsCmo-QRIPTzvN8n8mpE1qKDYqVIf9NBMtMlNWEco6oF2-aRlHaFTmZGqst4HLIby5FgwJ4BiNIpTyJjQ8dwAgA5OAz3kapImLYVqhp4dHogZ6-J8eGdbSbWkgKIaZa49HMextGM3EVXnddxASgp89qgAMTmOZqkKLlg62E2zYt0W4HuYkrT+-Jae0rOXOovHhOQn3fYL2kDaNoaFum1bNu2nWMyr7sOlOedjdN82to4LcCg26uTQAOjSDbyZcjbqNz19DtUIA)

### 💻 Code

This is the code for the slimmed-down variant. Note that the `createMachine2` is not a part of the repro case but it shows how putting the conditional type on a "different level" makes it work - which I find to be surprising because semantically both variants do the same thing

```ts
interface EventObject { type: string; }
interface TypegenDisabled { "@@xstate/typegen": false; }
interface TypegenEnabled { "@@xstate/typegen": true; }

type TypegenConstraint = TypegenEnabled | TypegenDisabled;

interface ActionObject<TEvent extends EventObject> {
  type: string;
  _TE?: TEvent;
}

declare function assign<TEvent extends EventObject>(
  assignment: (ev: TEvent) => void
): ActionObject<TEvent>;

declare function createMachine<
  TTypesMeta extends TypegenConstraint = TypegenDisabled
>(
  config: {
    types?: TTypesMeta;
  },
  action?: TTypesMeta extends TypegenEnabled
    ? { action: ActionObject<{ type: "WITH_TYPEGEN" }> }
    : { action: ActionObject<{ type: "WITHOUT_TYPEGEN" }> }
): void;

createMachine(
  {
    types: {} as TypegenEnabled,
  },
  {
    // `action` property is of a correct type - it expects `ActionObject<{ type: "WITH_TYPEGEN" }> }`
    action: assign((event) => {
      // but the type of this `assign` has been inferred to `ActionObject<{ type: "WITHOUT_TYPEGEN" } | { type: "WITH_TYPEGEN" }>`
      // so `event` here is of type `{ type: "WITHOUT_TYPEGEN" } | { type: "WITH_TYPEGEN" }`
      ((_accept: "WITH_TYPEGEN") => {})(event.type);
    }),
  }
);

// below we have the code that is not part of the repro case but which is semantically the same as the one above and yet it behaves differently

declare function createMachine2<
  TTypesMeta extends TypegenConstraint = TypegenDisabled
>(
  config: {
    types?: TTypesMeta;
  },
  action?: {
    // it works correctly if we check the `TTypesMeta` further down the road
    action: TTypesMeta extends TypegenEnabled
      ? ActionObject<{ type: "WITH_TYPEGEN" }>
      : ActionObject<{ type: "WITHOUT_TYPEGEN" }>;
  }
): void;

createMachine2(
  {
    types: {} as TypegenEnabled,
  },
  {
    action: assign((event) => {
      ((_accept: "WITH_TYPEGEN") => {})(event.type);
    }),
  }
);
```

The code below showcases more accurately what I'm trying to achieve.

<details>
<summary>Code from the originally reported playground</summary>

```ts
type Cast<T extends any, TCastType extends any> = T extends TCastType
  ? T
  : TCastType;
type Prop<T, K> = K extends keyof T ? T[K] : never;

type IndexByType<T extends { type: string }> = {
  [K in T["type"]]: Extract<T, { type: K }>;
};

interface EventObject {
    type: string;
}

interface TypegenDisabled {
  "@@xstate/typegen": false;
}
interface TypegenEnabled {
  "@@xstate/typegen": true;
}

type TypegenConstraint = TypegenEnabled | TypegenDisabled;

type ResolveTypegenMeta<
  TTypesMeta extends TypegenConstraint,
  TEvent extends { type: string }
> = TTypesMeta extends TypegenEnabled
  ? TTypesMeta & {
      indexedEvents: IndexByType<TEvent>;
    }
  : TypegenDisabled;

interface ActionObject<TEvent extends EventObject> {
  type: string;
  _TE?: TEvent;
}

interface MachineOptions<TEvent extends EventObject> {
  actions?: {
    [name: string]: ActionObject<TEvent>;
  };
}

type TypegenMachineOptions<
  TResolvedTypesMeta,
  TEventsCausingActions = Prop<TResolvedTypesMeta, "eventsCausingActions">,
  TIndexedEvents = Prop<TResolvedTypesMeta, "indexedEvents">
> = {
  actions?: {
    [K in keyof TEventsCausingActions]?: ActionObject<
      Cast<Prop<TIndexedEvents, TEventsCausingActions[K]>, EventObject>
    >;
  };
};

type MaybeTypegenMachineOptions<
  TEvent extends EventObject,
  TResolvedTypesMeta = TypegenDisabled
> = TResolvedTypesMeta extends TypegenEnabled
  ? TypegenMachineOptions<TResolvedTypesMeta>
  : MachineOptions<TEvent>;

declare function assign<TEvent extends EventObject>(
  assignment: (ev: TEvent) => void
): ActionObject<TEvent>;

declare function createMachine<
  TEvent extends { type: string },
  TTypesMeta extends TypegenConstraint = TypegenDisabled,
>(
  config: {
    schema?: {
      events?: TEvent;
    };
    types?: TTypesMeta;
  },
  options?: MaybeTypegenMachineOptions<TEvent, ResolveTypegenMeta<TTypesMeta, TEvent>>
): void;

interface TypesMeta extends TypegenEnabled {
  eventsCausingActions: {
    actionName: "BAR";
  };
}

createMachine(
  {
    types: {} as TypesMeta,
    schema: {
      events: {} as { type: "FOO" } | { type: "BAR"; value: string },
    },
  },
  {
    actions: {
      // `event` here is not narrowed down to the BAR one
      // yet the `actionName`'s type (available when I hover over the property) is correct
      actionName: assign((event) => {
        ((_accept: "BAR") => {})(event.type);
      }),
    },
  }
);
```

</details>

### 🙁 Actual behavior

`event` parameter here is not narrowed down to the WITH_TYPEGEN event, yet the `actionName`'s type (available when I hover over the property) is "correct" (only WITH_TYPEGEN event there) 

### 🙂 Expected behavior

I would expect this `event` parameter to be inferred correctly.

---

I've been debugging this for a bit and I think this is roughly what happens:
1. we have nested `chooseOverload`+`inferTypeArguments` calls because `assign` happens "within" `createMachine`
2. `typeParameters` for `assign` are assigned based on both branches of the conditional type - basically, a union of both is created for this position and since there is an overlap the reduced type gets assigned there.
3. the "routing" conditional type gets instantiated only after we exit the outer `inferTypeArguments` and it happens within `getSignatureApplicabilityError` but the `assign` has been instantiated and cached a step back - within `inferTypeArguments`
4. the inferred type for `assign` is never rechecked - so it stays as if it was supposed to handle both branches of the conditional type

Note that this might be highly incorrect as I'm not too familiar with the codebase