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

Type Inference Fails with Generic Types #60447

Open
maxnowack opened this issue Nov 7, 2024 · 3 comments
Open

Type Inference Fails with Generic Types #60447

maxnowack opened this issue Nov 7, 2024 · 3 comments
Labels
Needs More Info The issue still hasn't been fully clarified

Comments

@maxnowack
Copy link

πŸ”Ž Search Terms

error recursive generic types

πŸ•— Version & Regression Information

  • This changed between versions v5.7.0-beta and v4.1.5

⏯ Playground Link

https://www.typescriptlang.org/play/?ts=5.6.3#code/C4TwDgpgBAQghgZwgSWBAtgHgCpQLxRwB2IAfPlAN5QCWAJgFxS4C+AUGzUWgE4BmcAMbQAykThgEACwD2wHFAgAPNEToJYiFGizJyBeElQYANFGQViZKmyi1GUBMB5cA5idtRgNdBCZEAV3QAIwgeDztBGQAbaIhBbxkiADk4XyYnFyJ3TxodBCZsAG0AXQ92NiiiJ0cIOISZHiYAMWi4YABFALCQTDEJaTlMQ20MUn0qKCjY+MSUtL8oAHJEOj4lqHYAei2oAD09th2oZJlFHh5GqEBQcg4+AKIEmiSvCCdMY3RscGhlVXVNEYdB86N9IKQzMhQT9LCRSAAKACUNjsoEg5h0AGkICANAQACJyU7AdrPIjY3F9cSSWTyT5giCQ6Hg0ieY4HNm7ABE9C5UAAPlAud5fHzBVzpvU5qlRQKhXkMAgxVAAAYK9AIAB0ABJKJk3CwVZ5PGjoABhGJSskyiAMiitdqqTAAcQg8n6NKG9J+TIZEOWktm1oWS3GnP2hzsxy5+uyXI4kSSNVcECIYRoghEdVmjRabU63R4vQ9gzpOgZvp+4wo1EDTySNqYKwQaw27Cjuw5HagAFELldADLkhRhS1rlqDDYWGWcbgA3JsNjQNEQ5IQEAgaK5xME4l4zqblg6Cz0qQNaR9yz7zMyIOMlpq2BVjgBVbzRPIgLww4BnQRSeIANa0HwhBfuiS6EEQhAXHAn5XDIwQAFazA+B7IAgADySGzDgEy4H8qYAghyEJFAAD8UDwvhKiERoACCMG9FY5AUQI0RIFATDON0yJMGxSAcMcABK8QBDwG4AG7QAeP5QCmaY8O00B0HIAC0K4knMUAATiCBsAehLAMSpJJBSCC4TWnhFJitBQTpIAyCBuAAGSUbGrhyoEIRhIiJQMJ4xSYiUig0Wo9GMZgXB8GEUDPqy3bICBeRLBoxDQYpIBmFwgjRAEdDQMEchSF4-62flSiQXQJXQNqUAAO40NEdCCHAPB0J4dgUSquqYoacrdZQvU6iq-U9SwOqUIZxlzGZmBxYa4aJbQwApZBUDEbMZg8KJ4nQFwsl5B1nHmJh2EJDg1klOQBFhV4PDdEdnWqmNI2CgNQ26lNcgmeSukXUFpALXYwPHe9fXHCMUxaEwABSAQ1NtwBiVBwClfZj5FPZjnMFArnwu5nlBKEPC+RwB4iGA77uiF-waO5Zj4nUPgKjwNO0Y4M7ZPongiGzt0DVFMUABIQHAdAsJ9TPoCzEuUILrPYHAjVA8DFFFCLYtmJq2sU1TOBK9EDNSyzpAlEdTBFCIZv6TCrrAAyMAgAACq1wDmdgZguzwbt8wC21i0k0Sfu5pTc3YXs+zdAJFPLUAa3QWva7HiuNWbIMUfHvsaFjTmPeRUB2w7zuu+76ui3QZS9ioinnSnhsc1krih-FIPHZnUcaEs2pLHnT3UbTUAMRlkVENFrNxb3qsF26RcR+Zz5mD21dCPIddmCHV0t63wP+BAUk8JPu-7+bzBk7bbo4J77TFR3DduGH0-2z8jtz5fUC63kmAu6jZj3qGrI23REeJ02A8JZ0giAUo+digAAZgqFDPkA-MXQTygMsnYIoTtbJQC+ppMks1QElDInmR0qYXQXw9lAJ2YZ2BAA

πŸ’» Code

type BaseItem<T = any> = { id: T }

interface Snapshot<T extends BaseItem<I> = BaseItem, I = any> {
  id: string,
  time: number,
  collectionName: string,
  items: T[],
}

const selector: FlatQuery<Snapshot<BaseItem>> = { collectionName: 'asdf' }
// ^^
// No error βœ…

function test<ItemType extends BaseItem<IdType>, IdType = any>() {
  type ItemKeys = DotNotationKeys<Snapshot<ItemType, IdType>>
  // ^^
  // "id" | "time" | "collectionName" | "items" | `items.${string}`
  
  type CollectionNameType = Flatten<Get<Snapshot<ItemType, IdType>, 'collectionName'>>
  // ^^
  // "string"

  const genericSelector: FlatQuery<Snapshot<ItemType, IdType>> = { collectionName: 'asdf' }
  // ^^
  // Error ❌: Type '{ collectionName: string; }' is not assignable to type 'FlatQuery<Snapshot<ItemType, IdType>>'.
}

// Utility type to check if a type is an array or object.
type IsObject<T> = T extends object ? (T extends Array<any> ? false : true) : false

// Recursive type to generate dot-notation keys
type DotNotationKeys<T> = {
  [K in keyof T & (string | number)]:
  T[K] extends Array<infer U>
  // If it's an array, include both the index and the $ wildcard
    ? `${K}` | `${K}.$` | `${K}.${DotNotationKeys<U>}`
  // If it's an object, recurse into it
    : IsObject<T[K]> extends true
      ? `${K}` | `${K}.${DotNotationKeys<T[K]>}`
      : `${K}` // Base case: Just return the key
}[keyof T & (string | number)]

type Split<S extends string, Delimiter extends string> =
  S extends `${infer Head}${Delimiter}${infer Tail}`
    ? [Head, ...Split<Tail, Delimiter>]
    : [S]

type GetTypeByParts<T, Parts extends readonly string[]> =
  Parts extends [infer Head, ...infer Tail]
    ? Head extends keyof T
      ? GetTypeByParts<T[Head], Extract<Tail, string[]>>
      : Head extends '$'
        ? T extends Array<infer U>
          ? GetTypeByParts<U, Extract<Tail, string[]>>
          : never
        : never
    : T

type Get<T, Path extends string> =
  GetTypeByParts<T, Split<Path, '.'>>

type Flatten<T> = T extends any[] ? T[0] : T

type FlatQuery<T> = {
  [P in DotNotationKeys<T>]?: Flatten<Get<T, P>>
}

πŸ™ Actual behavior

Typescript isn't able to assign { collectionName: 'asdf' } when using generic types:

function test<ItemType extends BaseItem<IdType>, IdType = any>() {
  const selector: FlatQuery<Snapshot<ItemType, IdType>> = { collectionName: 'asdf' }
  // ^^
  // Type '{ collectionName: string; }' is not assignable to type 'FlatQuery<Snapshot<ItemType, IdType>>'.
}

Without generic types it work seamlessly:

const selector: FlatQuery<Snapshot<BaseItem>> = { collectionName: 'asdf' }

πŸ™‚ Expected behavior

{ collectionName: 'asdf' } should be successfully assignable to FlatQuery<Snapshot<ItemType, IdType>> using generic types.

Additional information about the issue

Related issue: maxnowack/signaldb#1030

@maxnowack maxnowack changed the title Nested generic types not Type Inference Fails with Generic Types Nov 7, 2024
@RyanCavanaugh RyanCavanaugh added the Needs More Info The issue still hasn't been fully clarified label Nov 7, 2024
@RyanCavanaugh
Copy link
Member

We really need a less (much less) complex repro in order to have any chance of investigating this before other issues which have much more straightforward repros. Please try to get this down to a minimal sample if you'd like us to look at it. Thanks!

@maxnowack
Copy link
Author

Okay. Iβ€˜ll try to strip it down as much as possible and update the issue

@mkantor
Copy link
Contributor

mkantor commented Nov 10, 2024

I'm not the person who opened this issue, but ended up looking at it and I think the core problem essentially boils down to this:

type Example<T extends 'b' | 'c'> =
  Partial<{ a: unknown } & Record<T, unknown>>

<T extends 'b' | 'c'>() => {
  const problem: Example<T> = { a: null }
//      ^^^^^^^
// Type '{ a: null; }' is not assignable to type 'Partial<{ a: unknown; } & Record<T, unknown>>'.
}

Any instantiated Example<ConcreteType> will allow an object containing only the key a to be assigned to it, so maybe Example<T> (where T is a type parameter) should also allow it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs More Info The issue still hasn't been fully clarified
Projects
None yet
Development

No branches or pull requests

3 participants