Repository: TypeScript
Issue: Improve reverse mapped types inference by creating candidates from concrete index types
Labels: Suggestion,Experimentation Needed

# Suggestion

## 🔍 Search Terms

inference, reverse mapped types, schema

## ✅ Viability Checklist

My suggestion meets these guidelines:

* [x] This wouldn't be a breaking change in existing TypeScript/JavaScript code
* [x] This wouldn't change the runtime behavior of existing JavaScript code
* [x] This could be implemented without emitting different JS based on the types of the expressions
* [x] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
* [x] This feature would agree with the rest of [TypeScript's Design Goals](https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals).

## ⭐ Suggestion

It would be great if TypeScript could take into account concrete index types when inferring reverse mapped types.

Reverse mapped types are a great technique that allows us to create dependencies between properties in complex objects.

For example, [in here](https://www.typescriptlang.org/play?ts=5.0.0-dev.20221121#code/C4TwDgpgBAysCGwIB4AqA+KBeKBvAUFFAJYB2xwx8ANgPwBcUA1hCAPYBmUqA3IVAGcESAQzz8iAbQDSJUs1aduAXUZxEKVDOWYAZOKKGobUmIBKEAMZsATgBNkQm2QDmAGgXsuGPkYC+vlAB+MH4dlbU8DbQHACupJaUJlCW0RoAsvCWABZkmugAFNakHMQuasL5AJSMvPj4qRAZWbmkEAUERGQUVNSMAETw-W78QhoCjJ2G8JMShiazRkYAcgCiABqoA8NzRH4j-geGAEaLS92UNANtQhB2O0vGpGePa5vbR4e7gpUTBo9QG5IOwvAELf4AwyoVYwLZQfpAu4PSFBT5LfbfDFfPYjPxVPhAA) we can validate what strings can be used as `initial` property on **any given level** of this object. We can also "target" sibling keys (from the parent object) within the `on` property.

This type of inference starts to break though once we add a constraint to `T` in order to access some of its **known** properties upfront. Things like `T[K]["type"]` prevents `T` from being inferred because the implemented "pattern matching" isn't handling this case and without a special handling this introduces, sort of, a circularity problem. Note how this doesn't infer properly based on the given argument: [here](https://www.typescriptlang.org/play?ts=5.0.0-dev.20221121#code/C4TwDgpgBAysCGwIBVzQLxQERngJ3gBtCJCsoAfbAMwEsA7I8qrAYwHsBbMdgV3oAmzbIi61WWANwAoUJFgIkMVgAsIneFEwAlCBzwCAPAGdgeBgHMANFADeUORABcCxCjRQAvgD4Z0gXqE+NDU-KzAtOz0UKx4EG4AsvCqDBCGyFAQAB5IgsauSqrq8N4AFNJQUOwARgBWLrYVlVAA2gDSUAxQANYQIOzUUMgAug1NzQ5oLsjtwy1YjljDMhOeK17SAJTTfhz0plBxxryEwFoxcYnJKqmljZXU7OxjE44uWKKc4lhWTZ6-nk2fiOJ2AMgA9OCoAA9AD80iAA)

I think there is a great potential here if we'd consider those accessed while inferring.

## 📃 Motivating Example

```ts
interface QueryFunctionContext<
  TQueryKey extends string,
> {
  queryKey: TQueryKey
}

type QueryOptions = {
    key: string
    data?: unknown;
    fnData?: unknown;
  }

type UseQueriesOptions<T extends ReadonlyArray<QueryOptions>> = {
  [K in keyof T]: {
    queryKey: T[K]['key']
    queryFn?: (
      ctx: QueryFunctionContext<T[K]['key']>,
    ) => Promise<T[K]['fnData']> | T[K]['fnData']
    select?: (data: T[K]['fnData']) => T[K]['data']
  }
}

declare function useQueries<
  T extends ReadonlyArray<QueryOptions>
>(queries: [...UseQueriesOptions<T>]): void;

```

<details>
<summary>Old example</summary>

I understand this this particular example looks complex. I'm merely using it as a motivating example to showcase what I'm trying to do:
- limit what kind of values are possible for the `initial` property (based on the keys of the inferred object)
- make this property available conditionally - it shouldn't be allowed where the `type` property of the "current" object is `'paralel'`

A way simpler demo of the inference algorithm shortcomings for this kind of things has been mentioned above ([playground link](https://www.typescriptlang.org/play?ts=5.0.0-dev.20221121#code/C4TwDgpgBAysCGwIBVzQLxQERngJ3gBtCJCsoAfbAMwEsA7I8qrAYwHsBbMdgV3oAmzbIi61WWANwAoUJFgIkMVgAsIneFEwAlCBzwCAPAGdgeBgHMANFADeUORABcCxCjRQAvgD4Z0gXqE+NDU-KzAtOz0UKx4EG4AsvCqDBCGyFAQAB5IgsauSqrq8N4AFNJQUOwARgBWLrYVlVAA2gDSUAxQANYQIOzUUMgAug1NzQ5oLsjtwy1YjljDMhOeK17SAJTTfhz0plBxxryEwFoxcYnJKqmljZXU7OxjE44uWKKc4lhWTZ6-nk2fiOJ2AMgA9OCoAA9AD80iAA))

```ts
type IsNever<T> = [T] extends [never] ? true : false;

type StateType = "parallel" | "final" | "compound" | "atomic";

type StateDefinition = {
  type?: StateType;
  states?: Record<string, StateDefinition>;
};

type State<T extends StateDefinition> = (T["type"] extends
  | "parallel"
  | undefined
  ? {}
  : IsNever<keyof T["states"]> extends false
  ? { initial: keyof T["states"] }
  : {}) & {
  type?: T["type"];
  states?: {
    [K in keyof T["states"]]: State<T["states"][K] & {}> & {
      on?: Record<string, keyof T["states"]>;
    };
  };
};

declare function createMachine<T extends StateDefinition>(config: State<T>): T;

createMachine({
  // initial property should be available if there are any nested states and if the `type` of this object is not `'parallel'`
  initial: "a", 
  states: {
    a: {},
  },
});
```

</details>

## 💻 Use Cases

Schema-like APIs could leverage this a ton:
- we could use is at XState to type our state machines
- libraries related to JSON schema could use this
- I bet that TanStack libraries could think of neat ways to leverage this
- I'm pretty sure that Redux Toolkit could use this **instead of** "validating things through intersections"

## Implementation

I'm willing to work on the implementation but I could use help with figuring out the exact constraints of the algorithm.

I've created locally a promising spike by gathering potential properties on the inference info when the `objectType` has available index info in this inference round ([here](https://github.dev/microsoft/TypeScript/blob/12d7e4bdbf98a877d27df6e8b072d663c839c0b8/src/compiler/checker.ts#L23844-L23854)) and creating a type out of those when there is no other candidate for it [here](https://github.dev/microsoft/TypeScript/blob/12d7e4bdbf98a877d27df6e8b072d663c839c0b8/src/compiler/checker.ts#L23494)