Skip to content

Recursive type definitions regression in 3.4.0 #30841

Description

@TerenceZ

TypeScript Version: 3.4.0-dev.201xxxxx

Search Terms:

Recursive
Conditional

Code

type Action<
  Args extends any[],
  Payload extends Record<string, any>,
  T extends string
> = ((...args: Args) => (Payload & { type: T })) & {
  readonly type: T
}

type AnyAction = Action<any[], any, string>

type CascadingActionMap = {
  [name: string]: AnyAction | CascadingActionMap
}

type Reducer<S, T> = (state: S, action: T) => S

type CascadingActionType<T> = T extends CascadingActionMap
  ? { [K in keyof T]: ActionType<T[K]> }[keyof T]
  : never

export type ActionType<T> = T extends AnyAction
  ? ReturnType<T>
  : T extends Reducer<any, infer A>
  ? A
  : CascadingActionType<T>

Expected behavior:
No errors. It's worth noting that it works in 3.3.4, with the correct derived type.

Actual behavior:
Running tsc command line, it reports the following errors:

error TS2456: Type alias 'CascadingActionType' circularly references itself.

914 type CascadingActionType<T> = T extends CascadingActionMap
         ~~~~~~~~~~~~~~~~~~~

error TS2315: Type 'ActionType' is not generic.

915   ? { [K in keyof T]: ActionType<T[K]> }[keyof T]
                          ~~~~~~~~~~~~~~~~

error TS2456: Type alias 'ActionType' circularly references itself.

918 export type ActionType<T> = T extends AnyAction
                ~~~~~~~~~~

error TS2315: Type 'CascadingActionType' is not generic.

924   : CascadingActionType<T>
        ~~~~~~~~~~~~~~~~~~~~~~

Playground Link:

Related Issues:

Activity

  1. JakeTunaley commented on Apr 10, 2019

    @JakeTunaley

    Recursive type aliases are, unfortunately, not supported - see #3496, #6230, and #24897 (comment).

  2. TerenceZ commented on Apr 10, 2019

    @TerenceZ
    Author

    Recursive type aliases are, unfortunately, not supported - see #3496, #6230, and #24897 (comment).

    However, the code above did work before 3.4.0, and the infered type is correct.

  3. RyanCavanaugh commented on Apr 10, 2019

    @RyanCavanaugh
    Member

    This didn't actually work in the past, you were just getting a silent any in most cases. Conditional types are now simplified more often and TS may detect a circularity earlier than before.

  4. TerenceZ commented on Apr 11, 2019

    @TerenceZ
    Author

    This didn't actually work in the past, you were just getting a silent any in most cases.

    Nope. The following derived Types works as expected before 3.4.0 (test in 3.3.4), and no silent any.

    const makeAction = <T extends string, P extends object, Args extends any[]>(
      type: T,
      fn: (...args: Args) => P,
    ) => {
      const action = (...args: Args) => {
        const ret = fn(...args) as P & { type: T }
        ret.type = type
        return ret
      }
      action.type = type
      return action
    }
    
    const actions = {
      a: makeAction('a', () => ({})),
      b: makeAction('b', (a: number, b: string) => ({ a, b })),
    }
    
    type Types = ActionType<typeof actions>
    
    const fn = (data: Types) => data
    
    fn(actions.a()) // work as expected
    fn(actions.b(1, 'a')) // work as expected
    fn(actions.c()) // ts error due to Property 'c' does not exist
    fn({ type: 'c' }) // ts error due to Type '"c"' is not assignable to type '"a" | "b"'.ts(2322)

    The Types is infered as expected (infered info from VSCode).

    type Types = {
        type: "a";
    } | ({
        a: number;
        b: string;
    } & {
        type: "b";
    })

    Conditional types are now simplified more often and TS may detect a circularity earlier than before.

    It means that ts >= 3.4.0 will make the above code illegal intentionally (because of detecting a circularity earlier than before)?

  5. typescript-bot commented on Apr 13, 2019

    @typescript-bot
    Contributor

    This issue has been marked as a 'Duplicate' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

  6. locked as resolved and limited conversation to collaborators on Oct 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    DuplicateAn existing issue was already created

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions