question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. ItĀ collects links to all the places you might be looking at while hunting down a tough bug.

And, if youā€™re still stuck at the end, weā€™re happy to hop on a call to see how we can help out.

Inferences made when trying to match an overload are carried over to matching consecutive overloads

See original GitHub issue

Bug Report

šŸ”Ž Search Terms

overload inference generic consecutive subsequent following

the only issue that I have found that might be related to this is this one but I canā€™t assess on my own if they are the same or not

šŸ•— Version & Regression Information

This is the behavior present in all 4.x versions available on the playground, including 4.5-beta.

āÆ Playground Link

Playground link with relevant code

šŸ’» Code

interface TypegenDisabled {
  "@@xstate/typegen": false;
}
interface TypegenEnabled {
  "@@xstate/typegen": true;
}
interface EventObject {
  type: string;
}
interface ActionObject<TEvent extends EventObject> {
  _TE: TEvent;
}

interface StateMachine<
  TEvent extends EventObject,
  TTypesMeta = TypegenDisabled
> {
  _TE: TEvent;
  _TRTM: TTypesMeta;
}

interface MachineOptions<TEvent extends EventObject> {
  action?: ActionObject<TEvent>;
}

type MaybeTypegenMachineOptions<
  TEvent extends EventObject,
  TTypesMeta = TypegenDisabled
> = TTypesMeta extends TypegenEnabled
  ? {
      action?: ActionObject<{ type: "WITH_TYPEGEN" }>;
    }
  : MachineOptions<TEvent>;

declare function assign<TEvent extends EventObject>(
  assignment: (ev: TEvent) => void
): ActionObject<TEvent>;

// atm I have a single signature and it **matches**
// however, if I uncomment this additional overload then **no signature** matches
// if later on I reorder the signatures then the first one (the one that is currently not commented out) matches

// what happens here is that some inferences made when attempting to match the first overload are cached 
// and "carried over" to matching the second overload - which makes the whole thing fail

// declare function useMachine<
//   TEvent extends EventObject,
//   TTypesMeta extends TypegenEnabled
// >(
//   getMachine: StateMachine<TEvent, TTypesMeta>,
//   options: MaybeTypegenMachineOptions<TEvent, TTypesMeta>
// ): { first: true };
declare function useMachine<TEvent extends EventObject>(
  getMachine: StateMachine<TEvent>,
  options?: MachineOptions<TEvent>
): { second: true };

const machine = {} as StateMachine<{ type: "WITHOUT_TYPEGEN" }>;

const ret = useMachine(machine, {
  action: assign((_ev) => {
    ((_type: "WITHOUT_TYPEGEN") => null)(_ev.type);
  }),
});

šŸ™ Actual behavior

No overload signature matches

šŸ™‚ Expected behavior

I would expect a signature to match when itā€™s used as one of the overloads if it matches on its own. And I would expect for inferences made in a failed attempt to match a signature to have no effect on matching the following signatures.

Issue Analytics

  • State:open
  • Created 2 years ago
  • Comments:10 (10 by maintainers)

github_iconTop GitHub Comments

2reactions
Andaristcommented, Mar 29, 2022

@AlCalzone I donā€™t believe those are the same. Iā€™ve created even more simplified demo of your issue:

declare function acceptMaybeNumber(arg: number): void
declare function acceptMaybeNumber(arg: undefined): void

declare const arg: number | undefined

acceptMaybeNumber(arg) // this errors

Playground link

The problem here is that each overload is type-checked separately and your argument that is a union doesnā€™t satisfy any of those overloads on its own. TypeScript doesnā€™t try to match this argument against all of the overloads at once.

As to my problemā€¦ Iā€™ve spent a significant amount of time investigating this in the debugger. And the root reason behind this issue is quite mundane - itā€™s caching (at least to some extent).

The problem is that we are dealing with some nested calls here and while trying to choose the first overload the inferences are made for the inner assign call. The first overload doesnā€™t match but the inference made to assign persists in its .resolvedType (or a similar) - and when we get to matching the second overload this type is just being reused, causing this type error. If we could only smartly discard the cached value then it would ā€œjust workā€. Iā€™ve even managed to bypass this prtical cache (conditionally), just as an experiment, but Iā€™ve then hit another layer of caching elsewhere and I gave up.

0reactions
unionalcommented, May 1, 2022

The actual error is here: https://github.com/unional/events-plus/blob/fsa-error/ts/supportFSA.spec.ts#L43

The type of event is const event: FSAEventBare<string> | FSAEventWithErrorPayload<string, Error>.

You can see that calling with no param is fine as in https://github.com/unional/events-plus/blob/fsa-error/ts/supportFSA.spec.ts#L32

because I have declare a the signature of () => FSA<...> on both interfaces.

But the return type of event({ error: true }) is wrong (it gets the return type FSA<...>, the return type of () => ..., instead of ErrorFSABare<>)

You can see the error directly in sandbox

Iā€™m trying to reduce that to a simpler example.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Need a dummy overload to improve inference Ā· Issue #39850 ...
Andarist mentioned this issue on Oct 11, 2021. Inferences made when trying to match an overload are carried over to matching consecutive overloads...
Read more >
Issues Ā· microsoft/TypeScript Ā· GitHub
Inferences made when trying to match an overload are carried over to matching consecutive overloads Bug A bug in TypeScript Needs Investigation This...
Read more >
Creating Mock Classes
To disambiguate functions overloaded on the const-ness of this object, use the Const() argument wrapper. using ::testing::ReturnRef; class MockFoo : public FooĀ ...
Read more >
Overload and Overcurrent Protection ā€“ Basic Motor Control
It is caused by excessive amounts of current drawn by a motor, which may be as high as six times the rated current....
Read more >
gMock Cookbook | GoogleTest
When a mock method is called, the last matching expectation that's still active will be selected (think ā€œnewer overrides olderā€). So, you can...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found