Skip to content

Declaration order dependence when inferring to a union #45603

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

Closed
afonsof opened this issue Aug 27, 2021 · 5 comments · Fixed by #46392
Closed

Declaration order dependence when inferring to a union #45603

afonsof opened this issue Aug 27, 2021 · 5 comments · Fixed by #46392
Assignees
Labels
Bug A bug in TypeScript Fix Available A PR has been opened for this issue

Comments

@afonsof
Copy link

afonsof commented Aug 27, 2021

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)
@MartinJohns
Copy link
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

@andrewbranch andrewbranch 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 Aug 27, 2021
@andrewbranch
Copy link
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).

@andrewbranch andrewbranch added the Bug A bug in TypeScript label Aug 27, 2021
@andrewbranch andrewbranch added this to the TypeScript 4.5.0 milestone Aug 27, 2021
@ahejlsberg ahejlsberg assigned ahejlsberg and unassigned weswigham Oct 15, 2021
@ahejlsberg
Copy link
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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Fix Available A PR has been opened for this issue
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants