Skip to content

Declaration order dependence when inferring to a union #45603

Closed

Description

@afonsof

Bug Report

🔎 Search Terms

complex types generic inference

🕗 Version & Regression Information

TS Version: ^4.2

  • This is a crash
  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about incorrect types function parameters

⏯ Playground Link

Playground link with relevant code

💻 Code

interface Action<TName extends string,TPayload> {
    name: TName,
    payload: TPayload
}

const actionA = { payload: 'any-string' } as Action<'ACTION_A', string>
const actionB = { payload: true } as Action<'ACTION_B', boolean>

function call<TName extends string,TPayload>(
  action: Action<TName,TPayload>,
  fn: (action: Action<TName,TPayload>)=> any,
) {
  fn(action)
}

const printFn = (action: typeof actionA | typeof actionB)=> console.log(action)

// It works
call(actionA, printFn)
// It crashes
call(actionB, printFn)

//workaround
call<'ACTION_B', boolean>(actionB, printFn)

Activity

  1. MartinJohns commented on Aug 27, 2021

    @MartinJohns
    Contributor

    There's some weird wonkyness in the Playground. Depending on whether you hover over the first or second call function call you get a different tooltip for the first call:

    image
    image

  2. changed the title [-]Cannot infer more than one generic type in a function that receives an object and another function as parameters[/-] [+]Declaration order dependence when inferring to a union[/+] on Aug 27, 2021
  3. andrewbranch commented on Aug 27, 2021

    @andrewbranch
    Member

    The thing that makes this particularly bad is that which call errors is dependent on whether actionA or actionB is declared first (and therefore the internal ordering of the union typeof actionA | typeof actionB).

  4. ahejlsberg commented on Oct 15, 2021

    @ahejlsberg
    Member

    Simpler repro:

    type A = { kind: 'a' };
    type B = { kind: 'b' };
    
    declare const a: A;
    declare const b: B;
    
    declare function fab(arg: A | B): void;
    
    declare function foo<T>(x: { kind: T }, f: (arg: { kind: T }) => void): void;
    
    foo(a, fab);  // Ok
    foo(b, fab);  // Error, but shouldn't be

    The issue here is that we make two contra-variant inferences for T (namely 'a' and 'b') and then pick one of the two based on the sort order of type identities--which so happens to be dictated by declaration order. We have logic to prefer a co-variant inference if it is a subtype of the contra-variant inference, but that happens after we've (randomly) picked one of the contra-variant inferences.

    What we really should do is check if the co-variant inference is a subtype of any of the contra-variant inferences, and, if so, go with the co-variant inference. The random picking should really be a last resort since we know it's going to lead to an error downstream (it's still preferable to intersecting the contra-variant inferences because that'll produce a never when the inferences are disjoint).

  5. 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

Labels

BugA bug in TypeScriptFix AvailableA PR has been opened for this issue

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions