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

Fixed compatibility with the upcoming TS 5.4 #4706

Merged
merged 4 commits into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/lucky-mice-lick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'xstate': patch
---

Fixed compatibility with the upcoming TypeScript 5.4
4 changes: 2 additions & 2 deletions packages/core/src/stateUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,7 @@ export function getStateNodes<
}
const subStateNodes = getStateNodes(
subStateNode,
stateValue[subStateKey]
stateValue[subStateKey]!
);

return allSubStateNodes.concat(subStateNodes);
Expand Down Expand Up @@ -684,7 +684,7 @@ export function transitionCompoundNode<
const childStateNode = getStateNode(stateNode, subStateKeys[0]);
const next = transitionNode(
childStateNode,
stateValue[subStateKeys[0]],
stateValue[subStateKeys[0]]!,
snapshot,
event
);
Expand Down
28 changes: 13 additions & 15 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ export type Actions<
export type StateKey = string | AnyMachineSnapshot;

export interface StateValueMap {
[key: string]: StateValue;
[key: string]: StateValue | undefined;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const m = setup({}).createMachine({
  initial: "foo",
  states: {
    foo: {
      initial: "f1",
      states: {
        f1: {},
        f2: {},
      },
    },
    baz: {
      initial: "b1",
      states: {
        b1: {},
        b2: {},
      },
    },
  },
});
const a = createActor(m)
const v = a.getSnapshot().value // const value: { foo?: "f1" | "f2" | undefined; baz?: "b1" | "b2" | undefined; }

As we can see here, the .value has optional properties here, because we can't be in both states at the same time. For that reason - this is actually more correct now. The older TS version had to gave up on some constraint checking or something and it allowed what shouldn't be allowed.

Note that some people requested to make this value type smth like:

{ foo: "f1" | "f2"; } | { baz: "b1" | "b2"; }

That wouldn't have optional properties at all. Unions like this are not discriminated and I found them to often be problematic when it comes to using/consuming them. So I'm not sold (yet?) on making this change. Either way, for the time being - without that proposed change - this is a correctness improvement.

}

/**
Expand Down Expand Up @@ -2352,21 +2352,19 @@ export type ToChildren<TActor extends ProvidedActor> =
// or maybe even `TActor["logic"]` since it's possible to configure `{ src: string; logic: SomeConcreteLogic }`
// TODO: consider adding `| undefined` here
Record<string, AnyActorRef>
: Compute<
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing Compute might degrade type displays slightly but I wasn't able to fix this in any different way. I think this might be a bug in TS that this doesn't typecheck - I reported it here: microsoft/TypeScript#57188

ToConcreteChildren<TActor> &
{
include: {
[id: string]: TActor extends any
? ActorRefFrom<TActor['logic']> | undefined
: never;
};
exclude: {};
}[undefined extends TActor['id'] // if not all actors have literal string IDs then we need to create an index signature containing all possible actor types
: ToConcreteChildren<TActor> &
{
include: {
[id: string]: TActor extends any
? ActorRefFrom<TActor['logic']> | undefined
: never;
};
exclude: {};
}[undefined extends TActor['id'] // if not all actors have literal string IDs then we need to create an index signature containing all possible actor types
? 'include'
: string extends TActor['id']
? 'include'
: string extends TActor['id']
? 'include'
: 'exclude']
>;
: 'exclude'];

export type StateSchema = {
states?: Record<string, StateSchema>;
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export function matchesState(
return false;
}

return matchesState(parentStateValue[key], childStateValue[key]);
return matchesState(parentStateValue[key]!, childStateValue[key]!);
});
}

Expand Down
Loading