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

.call on an union with different arguments counts fails when passing any #58468

Open
nicolo-ribaudo opened this issue May 8, 2024 · 2 comments · May be fixed by #57909 or #58482
Open

.call on an union with different arguments counts fails when passing any #58468

nicolo-ribaudo opened this issue May 8, 2024 · 2 comments · May be fixed by #57909 or #58482

Comments

@nicolo-ribaudo
Copy link

nicolo-ribaudo commented May 8, 2024

🔎 Search Terms

call union expected arguments but got

🕗 Version & Regression Information

Before version 4.5 both the .calls in the example below failed. Since then, .call(null, something-not-any) is accepted and .call(null, something-any) failes.

⏯ Playground Link

https://www.typescriptlang.org/play/?ts=5.5.0-beta#code/CYUwxgNghgTiAEYD2A7AzgF3gMxQLngApCBKeAXgD54A3JAS2DIB8jCoCUBXAWwCMQMMlVoMmAKHGhIsBMnRYAHp14CYAbinhocRKkzwAngSgpDm8bgB0YKBAiFu9gDTxFJTddv3HXF0Y9JXEJDQOD3CyA

💻 Code

declare const fn: (() => void) | ((a: number) => void)

declare const x: number;
declare const y: any;

fn.call(null, x); // ok
fn.call(null, y); // error

fn(y); // ok
fn(x); // ok

🙁 Actual behavior

The line marked as // error gives the following error:

Expected 1 arguments, but got 2.

🙂 Expected behavior

It should be accepted, the same as the other lines are accepted.

Additional information about the issue

No response


TODO for myself: Remove @ts-ignore in https://github.com/babel/babel/blob/main/packages/babel-generator/src/printer.ts#L717 once this is fixed

@Andarist
Copy link
Contributor

Andarist commented May 8, 2024

I love fun issues. Expect me digging into this one later 😉

You have my attention

@Andarist
Copy link
Contributor

Andarist commented May 9, 2024

So in this case we end up with multiple inference candidates. We have:

  • covariant: [any]
  • contravariant: [] and [a: number]

When multiple inference candidates are present, TS tries to select the best match based on different criteria. Since contravariant candidates don't overlap sufficiently enough the first one gets picked as overall inferredContravariantType. Not: This is order-dependent so when you switch the order of ur union members it magically works because the other one gets picked there.

Since we have both inferredCovariantType and inferredContravariantType we also need to pick between those 2. It happens so that preferCovariantType gets computed as false in your case. It's rejected based on this check:

some(inference.contraCandidates, t => isTypeSubtypeOf(inferredCovariantType, t))

[any] is not a subtype of either of the contravariant candidates, it's not a subtype of [] and it's not a subtype of [a: number]. So it ends up picking the covariant candidate and it fails to typecheck ur arguments against inferred signature further down the line.

So it turns out one of my PR fixes this case alrady: #57909

But what happens in the implementation here got me thinking... why do we even have multiple contravariant candidates here? we do have that because the inference infer from each of your signatures separately here. This is kinda a single call though. TS is able to combine union (or intersection) signatures into one. Shouldn't we then infer from that combined signature here?

So that led me to creating this experiment that also addresses this issue: #58482

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants