From e0f8e17adaa643de9df51cac77bb0f0c390a7383 Mon Sep 17 00:00:00 2001 From: Matt Pocock Date: Tue, 12 Apr 2022 10:09:49 +0100 Subject: [PATCH 1/4] Added a failing test for inference with inline actions --- packages/core/test/actions.test.ts | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/packages/core/test/actions.test.ts b/packages/core/test/actions.test.ts index e5f809d4b1..d74645a471 100644 --- a/packages/core/test/actions.test.ts +++ b/packages/core/test/actions.test.ts @@ -1851,3 +1851,30 @@ describe('assign action order', () => { } ); }); + +describe('Inline actions & TypeScript', () => { + it('Should pick up context and events defined in schema', () => { + interface Context {} + + type Event = + | { type: 'EVENT_WITH_FLAG'; flag: boolean } + | { + type: 'EVENT_WITHOUT_FLAG'; + }; + + createMachine({ + schema: { + context: {} as Context, + events: {} as Event + }, + on: { + EVENT_WITH_FLAG: { + actions: (context, event) => { + ((_accept: 'EVENT_WITH_FLAG') => {})(event.type); + ((_accept: boolean) => {})(event.flag); + } + } + } + }); + }); +}); From 307b1fbee7cdcb66245c22671b0aa7a9912101b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sat, 16 Apr 2022 10:35:38 +0200 Subject: [PATCH 2/4] Fixed an issue with inline functions in the config object used as transition actions not having their argument types inferred --- packages/core/src/types.ts | 25 +++++++++------ packages/core/test/actions.test.ts | 27 ---------------- packages/core/test/types.test.ts | 51 ++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 37 deletions(-) diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 6395246cae..96a6ccc9ca 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -448,16 +448,21 @@ export type TransitionConfigOrTarget< TransitionConfigTarget | TransitionConfig >; -export type TransitionsConfigMap = { - [K in TEvent['type']]?: TransitionConfigOrTarget< - TContext, - TEvent extends { type: K } ? TEvent : never - >; -} & { - ''?: TransitionConfigOrTarget; -} & { - '*'?: TransitionConfigOrTarget; -}; +export type TransitionsConfigMap< + TContext, + TEvent extends EventObject +> = Compute< + { + [K in TEvent['type']]?: TransitionConfigOrTarget< + TContext, + TEvent extends { type: K } ? TEvent : never + >; + } & { + ''?: TransitionConfigOrTarget; + } & { + '*'?: TransitionConfigOrTarget; + } +>; type TransitionsConfigArray = Array< // distribute the union diff --git a/packages/core/test/actions.test.ts b/packages/core/test/actions.test.ts index d74645a471..e5f809d4b1 100644 --- a/packages/core/test/actions.test.ts +++ b/packages/core/test/actions.test.ts @@ -1851,30 +1851,3 @@ describe('assign action order', () => { } ); }); - -describe('Inline actions & TypeScript', () => { - it('Should pick up context and events defined in schema', () => { - interface Context {} - - type Event = - | { type: 'EVENT_WITH_FLAG'; flag: boolean } - | { - type: 'EVENT_WITHOUT_FLAG'; - }; - - createMachine({ - schema: { - context: {} as Context, - events: {} as Event - }, - on: { - EVENT_WITH_FLAG: { - actions: (context, event) => { - ((_accept: 'EVENT_WITH_FLAG') => {})(event.type); - ((_accept: boolean) => {})(event.flag); - } - } - } - }); - }); -}); diff --git a/packages/core/test/types.test.ts b/packages/core/test/types.test.ts index 33557207f1..7489dd6a5e 100644 --- a/packages/core/test/types.test.ts +++ b/packages/core/test/types.test.ts @@ -514,6 +514,57 @@ describe('events', () => { acceptMachine(toggleMachine); }); + + it('should infer inline function parameters when narrowing transition actions based on the event type', () => { + createMachine({ + schema: { + context: {} as { + count: number; + }, + events: {} as + | { type: 'EVENT_WITH_FLAG'; flag: boolean } + | { + type: 'EVENT_WITHOUT_FLAG'; + } + }, + on: { + EVENT_WITH_FLAG: { + actions: (_context, event) => { + ((_accept: 'EVENT_WITH_FLAG') => {})(event.type); + ((_accept: boolean) => {})(event.flag); + // @ts-expect-error + ((_accept: 'is not any') => {})(event); + } + } + } + }); + }); + + it('should infer inline function parameters when for a wildcard transition', () => { + createMachine({ + schema: { + context: {} as { + count: number; + }, + events: {} as + | { type: 'EVENT_WITH_FLAG'; flag: boolean } + | { + type: 'EVENT_WITHOUT_FLAG'; + } + }, + on: { + '*': { + actions: (_context, event) => { + ((_accept: 'EVENT_WITH_FLAG' | 'EVENT_WITHOUT_FLAG') => {})( + event.type + ); + // @ts-expect-error + ((_accept: 'is not any') => {})(event); + } + } + } + }); + }); }); describe('interpreter', () => { From 7a0bb3d514d6f17341945504ea9e5ee2b7f6d1ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sat, 16 Apr 2022 10:44:35 +0200 Subject: [PATCH 3/4] Fix small type errors --- .changeset/tasty-chicken-itch.md | 5 +++++ packages/core/src/StateNode.ts | 3 +-- packages/core/src/patterns.ts | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 .changeset/tasty-chicken-itch.md diff --git a/.changeset/tasty-chicken-itch.md b/.changeset/tasty-chicken-itch.md new file mode 100644 index 0000000000..91c5aea351 --- /dev/null +++ b/.changeset/tasty-chicken-itch.md @@ -0,0 +1,5 @@ +--- +'xstate': patch +--- + +Fixed an issue with inline functions in the config object used as transition actions not having their argument types inferred. diff --git a/packages/core/src/StateNode.ts b/packages/core/src/StateNode.ts index 32047ca815..a31a3a5db4 100644 --- a/packages/core/src/StateNode.ts +++ b/packages/core/src/StateNode.ts @@ -66,7 +66,6 @@ import { InvokeSourceDefinition, MachineSchema, ActorRef, - StateMachine, InternalMachineOptions, ServiceMap, StateConfig, @@ -306,7 +305,7 @@ class StateNode< private _context: | Readonly | (() => Readonly) = ('context' in config - ? (config as StateMachine).context + ? (config as any).context : undefined) as any, // TODO: this is unsafe, but we're removing it in v5 anyway _stateInfo?: { parent: StateNode; diff --git a/packages/core/src/patterns.ts b/packages/core/src/patterns.ts index 90681ff7dd..a20903e815 100644 --- a/packages/core/src/patterns.ts +++ b/packages/core/src/patterns.ts @@ -56,7 +56,7 @@ export function sequence< items.forEach((item, i) => { const state: StateNodeConfig = { - on: {} + on: {} as any }; if (i + 1 === items.length) { From 85c8e6f9a4841f9b8a217bb6f860ce647b966216 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sat, 16 Apr 2022 13:53:13 +0200 Subject: [PATCH 4/4] Try a solution based on merging the mapped type --- packages/core/src/patterns.ts | 2 +- packages/core/src/types.ts | 23 ++++++++--------------- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/packages/core/src/patterns.ts b/packages/core/src/patterns.ts index a20903e815..90681ff7dd 100644 --- a/packages/core/src/patterns.ts +++ b/packages/core/src/patterns.ts @@ -56,7 +56,7 @@ export function sequence< items.forEach((item, i) => { const state: StateNodeConfig = { - on: {} as any + on: {} }; if (i + 1 === items.length) { diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 96a6ccc9ca..4114a28187 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -448,21 +448,14 @@ export type TransitionConfigOrTarget< TransitionConfigTarget | TransitionConfig >; -export type TransitionsConfigMap< - TContext, - TEvent extends EventObject -> = Compute< - { - [K in TEvent['type']]?: TransitionConfigOrTarget< - TContext, - TEvent extends { type: K } ? TEvent : never - >; - } & { - ''?: TransitionConfigOrTarget; - } & { - '*'?: TransitionConfigOrTarget; - } ->; +export type TransitionsConfigMap = { + [K in TEvent['type'] | '' | '*']?: K extends '' | '*' + ? TransitionConfigOrTarget + : TransitionConfigOrTarget< + TContext, + TEvent extends { type: K } ? TEvent : never + >; +}; type TransitionsConfigArray = Array< // distribute the union