Skip to content

Improve inference of Recursive types #52722

@alesmenzel

Description

@alesmenzel

Suggestion

It would be nice to be able to infer all variants from a recursive type. Playground link.

// SETUP

// E.g. for nested selectable lists
type Recursive<Id> = {
    id: Id
    children: readonly Recursive<Id>[]
}

// Function to return all ids from the type
function getIds<Id>(items: readonly Recursive<Id>[]): Id[] {
    return [] // return value is not important
}

// EXAMPLE USAGE

const items = [{
    id: 'a',
    children: [{
        id: 'b',
        children: []
    }]
}] as const satisfies readonly Recursive<string>[]

const foo = getIds(items)
// EXPECTED: ("a" | "b")[] but returns just "a"[]

🔍 Search Terms

recursive types

✅ Viability Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
    It might be breaking from types perspective if some code relies on the current behaviour.
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

⭐ Suggestion

Generics inside recursive types would be expanded.

📃 Motivating Example

E.g. for selectable nested lists, we could provide auto-complete for onSelect(itemId: <union of all ids from recursive type>).

💻 Use Cases

I haven't found a workaround that would work for the getIds function in the example. One semi working workaround is written below - it returns correctly union of all ids literals, but TS shows an error and it cannot be used as return value for getIds(...) because then TS show that the type is recursive and possible infinite.

type GetRecursiveType<T extends readonly Recursive<any>[]> = T[number]['id'] | (T[number]['children'] extends undefined ? never : GetRecursiveType<T[number]['children']>)

Playground link.

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugA bug in TypeScriptHelp WantedYou can do this

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions