Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Obscure "type cannot be used to index type" error when a mapped type is nested into another mapped type #54289

Closed
shau-kote opened this issue May 17, 2023 · 8 comments
Labels
Duplicate An existing issue was already created

Comments

@shau-kote
Copy link

shau-kote commented May 17, 2023

Bug Report

🔎 Search Terms

  • mapped type
  • nested mapped type

🕗 Version & Regression Information

  • It reproduces in TS of versions from 3.8.3 to 5.0.4 (there are other compiler errors in earlier versions).
  • It reproduces in Nightly (v5.2.0-dev.20230516) as well.
  • I failed to find out a description of similar cases in the FAQ.

⏯ Playground Link

Playground link with relevant code

💻 Code

type Values<T extends object> = T[keyof T]

const ENTITY_CONFIG = {
  'entity1': {
    'step1':  {
      body: { aaa: 1 }
    },
    'step2':  {
      body: { bbb: 1 }
    },
  },
  'entity2': {
    'step1':  {
      body: { ccc: 1 }
    },
    'step2':  {
      body: { ddd: 1 }
    },
  }
} as const

type EntityConfig = typeof ENTITY_CONFIG

type EntityType = Values<{
  [T in keyof EntityConfig]: {
    entityName: T,

    step: Values<{
      [S in keyof EntityConfig[T]]: {
        stepName: S
        body: EntityConfig[T][S]['body'] // <-- TS2536 error
      }
    }>
  }
}>

🙁 Actual behavior

I see an error around the EntityConfig[T][S]['body'] type expression with the following error message: Type '"body"' cannot be used to index type '{ readonly entity1: { readonly step1: { readonly body: { readonly aaa: 1; }; }; readonly step2: { readonly body: { readonly bbb: 1; }; }; }; readonly entity2: { readonly step1: { readonly body: { readonly ccc: 1; }; }; readonly step2: { ...; }; }; }[T][S]'..

Firstly, I don't expect any errors in my type definition. Although it's possible of course that my mental model is wrong, or I don't understand some compiler limitations.

Secondly, the error message is quite obscure to me, it doesn't help to understand what I do wrong and how to fix it.

🙂 Expected behavior

I expect that the EntityType will be successfully constructed and will be something like this:

type EntityType = {
  entityName: 'entity1'
  step: {
    stepName: 'step1'
    body: { aaa: 1 }
  } | {
    stepName: 'step2'
    body: { bbb: 1 }
  }
} | {
  entityName: 'entity2'
  step: {
    stepName: 'step1'
    body: { ccc: 1 }
  } | {
    stepName: 'step2'
    body: { ddd: 1 }
  }
}

In fact, it seems to me based on my "tests" in the playground above that the EntityType type construction works well, despite the error in the type definition.

@shau-kote
Copy link
Author

shau-kote commented May 17, 2023

Also, I found out that it's possible to kinda suppress the error by using the EntityConfig[T][S] extends infer U ? U extends { body: infer B } ? B : never : never expression for specifying the type of the body property, but it seems to me as a weird and should-be-needless hack.

@shau-kote
Copy link
Author

Lastly, I tried to explicitly declare that all steps of all entities should have a body declaration — so it's safe to index any step definition by the 'body type.
But still no luck.

Playground Link

type Values<T extends object> = T[keyof T]

type Config = {
  readonly [entityName: string]: {
    readonly [stepName: string]: {
      readonly body: object
    }
  }
}

const ENTITY_CONFIG = {
  'entity1': {
    'step1':  {
      body: { aaa: 1 }
    },
    'step2':  {
      body: { bbb: 1 }
    },
  },
  'entity2': {
    'step1':  {
      body: { ccc: 1 }
    },
    'step2':  {
      body: { ddd: 1 }
    },
  }
} satisfies Config

type EntityConfig = typeof ENTITY_CONFIG

type EntityType = Values<{
  [T in keyof EntityConfig]: {
    entityName: T,

    step: Values<{
      [S in keyof EntityConfig[T]]: {
        stepName: S
        body: EntityConfig[T][S]['body']
      }
    }>
  }
}>

@Andarist
Copy link
Contributor

It's likely a duplicate of #21760

@shau-kote
Copy link
Author

Thank you for the quick answer, @Andarist!

It really looks like a related problem. It's very sad that there's no visible success in finding a solution in this area.

Could you please also let me know if you're aware of any other (maybe better) workarounds except the double extends infer I described above?

@Andarist
Copy link
Contributor

You could try using something like this if you think that it makes your life easier (TS playground):

type Tail<T extends any[]> = T extends [any, ...infer R] ? R : never;
type GetProp<T, K> = K extends keyof T ? T[K] : never;
type GetPath<T, P extends any[]> = P extends [any]
  ? GetProp<T, P[0]>
  : GetPath<GetProp<T, P[0]>, Tail<P>>;

type EntityType = Values<{
  [T in keyof EntityConfig]: {
    entityName: T;

    step: Values<{
      [S in keyof EntityConfig[T]]: {
        stepName: S;
        body: GetPath<EntityConfig, [T, S, "body"]>;
      };
    }>;
  };
}>;

@shau-kote
Copy link
Author

Thank you for the advice, @Andarist! I'll think about it — it looks great from the usage side, but the mutually-recursive implementation... oh, gods. 🥲

@shau-kote
Copy link
Author

I'm not sure whether it will be proper to close this issue now, but feel free to do it if it is.

@Andarist
Copy link
Contributor

Andarist commented May 17, 2023

I'm not sure whether it will be proper to close this issue now, but feel free to do it if it is.

I don't have such privileges - I think that the issue can be closed though (since it's a duplicate). Might be worth posting your case in that main thread though, this way it's likely that it will be added as a test case when that issue gets fixed.

@RyanCavanaugh RyanCavanaugh added the Duplicate An existing issue was already created label Jun 14, 2023
@RyanCavanaugh RyanCavanaugh closed this as not planned Won't fix, can't repro, duplicate, stale Jun 14, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

3 participants