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

Make it impossible to exit a root state #3487

Merged
merged 8 commits into from
Sep 12, 2022
Merged

Conversation

Andarist
Copy link
Member

@Andarist Andarist commented Aug 1, 2022

closes #3072

@changeset-bot
Copy link

changeset-bot bot commented Aug 1, 2022

🦋 Changeset detected

Latest commit: f9a8fde

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
xstate Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@codesandbox-ci
Copy link

codesandbox-ci bot commented Aug 1, 2022

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit f9a8fde:

Sandbox Source
XState Example Template Configuration
XState React Template Configuration
Unable to send event to child Issue #3072
Unable to send event to child (forked) Issue #3072

@ghost
Copy link

ghost commented Aug 1, 2022

👇 Click on the image for a new way to code review
  • Make big changes easier — review code in small groups of related files

  • Know where to start — see the whole change at a glance

  • Take a code tour — explore the change with an interactive tour

  • Make comments and review — all fully sync’ed with github

    Try it now!

Review these changes using an interactive CodeSee Map

Legend

CodeSee Map Legend

@@ -998,25 +1003,6 @@ export function removeConflictingTransitions<
return Array.from(filteredTransitions);
}

function findLCCA<TContext extends MachineContext, TEvent extends EventObject>(
Copy link
Member Author

@Andarist Andarist Aug 1, 2022

Choose a reason for hiding this comment

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

I've inlined this logic into getTransitionDomain because it now can return the root state. The LCCA stands for the Least Common Compound Ancestor. We were never limiting ourselves here to return a compound state - so the name was already incorrect. We could have changed it to findLCA where LCA stands for Least Common Ancestor. However, when returning the root state from here it's not always even an ancestor of the input stateNodes (for example, findLCA([root]) would return root and the root is not an ancestor of itself). So I've concluded that it's best to inline this function altogether to avoid confusion and debate.

current = current.filter((sn) => path.includes(sn));
}

const domain = current[current.length - 1];
Copy link
Member Author

Choose a reason for hiding this comment

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

We sort of had a bug here previously. We could return undefined from here, whereas the annotated return type is StateNode<TContext, TEvent> | null. Everything was working OK as .parent === domainButActuallyUndefined (and similar) was also a good stop for most of our internal while loops etc.

marker = marker.parent;
}

return path;
return path.reverse();
Copy link
Member Author

Choose a reason for hiding this comment

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

Conceptually it's a little bit better to push to an array than to shift it all the time (each shift has to move all the existing items). So I've switched to pushing here but to correct the actual result I need to .reverse() at the end.

Comment on lines +934 to +936
if (!marker) {
return [stateNode];
}
Copy link
Member Author

Choose a reason for hiding this comment

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

By always including the root here we don't allow it to be ever exited.

@@ -1578,7 +1582,8 @@ export function resolveMicroTransition<
currentState.context,
new Set(prevConfig),
machine,
_event
_event,
new Set(!currentState._initial ? [] : [machine.root])
Copy link
Member Author

Choose a reason for hiding this comment

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

By moving mutStatesToEnter initialization to here we make it easier to add the root as a state that has to be entered during the fake~ initial transition.

The usual expectation is that in a transition similar to this the addAncestorStatesToEnter should add all ancestors of the target state to mutStatesToEnter (if needed, ofc). However, this relies on getProperAncestors and that algorithm doesn't include the toStateNode in its result, so by using this logic alone we were not able to add the root state to the initial mutStatesToEnter and the root's invokes and entry actions rely on being included in this set for that initial transition.

SCXML doesn't account for this case in their algorithms because <scxml/> itself can't have <invoke/>, <onentry/>, etc. We are extending the spec by allowing those on our root state and that raises the need to make adjustments to the algorithms.

Copy link
Member

Choose a reason for hiding this comment

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

This was going to be my main question - where this differs from the SCXML spec etc. Thanks for this!

id: 'root',
invoke: {
src: () =>
fromPromise(() => {
Copy link
Member

Choose a reason for hiding this comment

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

Wondering if it would be better/more explicit to test using entry/exit rather than an invocation.

If for some reason, the implementation changes in the future that invocations are started async, then the test will still pass even though the faulty behavior will be there. (Not saying that it will, but just thinking about potential edge-cases)

Copy link
Member Author

Choose a reason for hiding this comment

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

I will add a test for entry/exit actions too 👍 Until we change how invocations are handled it's good to have the existing test in place too.

Copy link
Member

@davidkpiano davidkpiano left a comment

Choose a reason for hiding this comment

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

This LGTM; just a comment about the robustness of the test.

Andarist and others added 3 commits August 1, 2022 17:49
Co-authored-by: David Khourshid <davidkpiano@gmail.com>
# Conflicts:
#	packages/core/src/stateUtils.ts
@Andarist Andarist merged commit 1b6e3df into next Sep 12, 2022
@Andarist Andarist deleted the davidkpiano/3072-next branch September 12, 2022 15:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants