Skip to content

Commit

Permalink
[core] Fix Actor<T> not assignable to ActorRef<T> problem (#5011)
Browse files Browse the repository at this point in the history
* Add failing test

* Add stricter `ActorRefFromLogic`, improve `ActorRefFrom`

* Changeset

* Remove test
  • Loading branch information
davidkpiano authored Aug 9, 2024
1 parent 437738d commit a275d27
Show file tree
Hide file tree
Showing 9 changed files with 67 additions and 66 deletions.
5 changes: 5 additions & 0 deletions .changeset/tasty-kangaroos-lay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'xstate': patch
---

There is a new type helper: `ActorRefFromLogic<TLogic>`. This type is a stricter form of `ActorRefFrom<TLogic>` that only accepts actor logic types. See https://github.com/statelyai/xstate/issues/4997 for more details.
4 changes: 2 additions & 2 deletions packages/core/src/actors/callback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { XSTATE_STOP } from '../constants.ts';
import { AnyActorSystem } from '../system.ts';
import {
ActorLogic,
ActorRefFrom,
ActorRefFromLogic,
AnyActorRef,
AnyEventObject,
EventObject,
Expand Down Expand Up @@ -73,7 +73,7 @@ export type CallbackActorLogic<
export type CallbackActorRef<
TEvent extends EventObject,
TInput = NonReducibleUnknown
> = ActorRefFrom<CallbackActorLogic<TEvent, TInput>>;
> = ActorRefFromLogic<CallbackActorLogic<TEvent, TInput>>;

type Receiver<TEvent extends EventObject> = (
listener: {
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/actors/observable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { XSTATE_STOP } from '../constants';
import { AnyActorSystem } from '../system.ts';
import {
ActorLogic,
ActorRefFrom,
ActorRefFromLogic,
EventObject,
NonReducibleUnknown,
Snapshot,
Expand Down Expand Up @@ -67,7 +67,7 @@ export type ObservableActorLogic<
* @see {@link fromObservable}
* @see {@link fromEventObservable}
*/
export type ObservableActorRef<TContext> = ActorRefFrom<
export type ObservableActorRef<TContext> = ActorRefFromLogic<
ObservableActorLogic<TContext, any>
>;

Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/actors/promise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { XSTATE_STOP } from '../constants.ts';
import { AnyActorSystem } from '../system.ts';
import {
ActorLogic,
ActorRefFrom,
ActorRefFromLogic,
AnyActorRef,
EventObject,
NonReducibleUnknown,
Expand Down Expand Up @@ -61,7 +61,7 @@ export type PromiseActorLogic<
*
* @see {@link fromPromise}
*/
export type PromiseActorRef<TOutput> = ActorRefFrom<
export type PromiseActorRef<TOutput> = ActorRefFromLogic<
PromiseActorLogic<TOutput, unknown>
>;

Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/actors/transition.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AnyActorSystem } from '../system.ts';
import {
ActorLogic,
ActorRefFrom,
ActorRefFromLogic,
ActorScope,
EventObject,
NonReducibleUnknown,
Expand Down Expand Up @@ -86,7 +86,7 @@ export type TransitionActorLogic<
export type TransitionActorRef<
TContext,
TEvent extends EventObject
> = ActorRefFrom<
> = ActorRefFromLogic<
TransitionActorLogic<TransitionSnapshot<TContext>, TEvent, unknown>
>;

Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/spawn.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ProcessingStatus, createActor } from './createActor.ts';
import {
ActorRefFrom,
ActorRefFromLogic,
AnyActorLogic,
AnyActorRef,
AnyActorScope,
Expand Down Expand Up @@ -43,7 +43,7 @@ export type Spawner<TActor extends ProvidedActor> = IsLiteralString<
<TSrc extends TActor['src']>(
logic: TSrc,
...[options]: SpawnOptions<TActor, TSrc>
): ActorRefFrom<GetConcreteByKey<TActor, 'src', TSrc>['logic']>;
): ActorRefFromLogic<GetConcreteByKey<TActor, 'src', TSrc>['logic']>;
<TLogic extends AnyActorLogic>(
src: TLogic,
options?: {
Expand All @@ -52,7 +52,7 @@ export type Spawner<TActor extends ProvidedActor> = IsLiteralString<
input?: InputFrom<TLogic>;
syncSnapshot?: boolean;
}
): ActorRefFrom<TLogic>;
): ActorRefFromLogic<TLogic>;
}
: <TLogic extends AnyActorLogic | string>(
src: TLogic,
Expand All @@ -62,7 +62,7 @@ export type Spawner<TActor extends ProvidedActor> = IsLiteralString<
input?: TLogic extends string ? unknown : InputFrom<TLogic>;
syncSnapshot?: boolean;
}
) => TLogic extends string ? AnyActorRef : ActorRefFrom<TLogic>;
) => TLogic extends AnyActorLogic ? ActorRefFromLogic<TLogic> : AnyActorRef;

export function createSpawner(
actorScope: AnyActorScope,
Expand Down
59 changes: 15 additions & 44 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2006,50 +2006,19 @@ export type ActorLogicFrom<T> = ReturnTypeOrValue<T> extends infer R
: never
: never;

// TODO: in v6, this should only accept AnyActorLogic, like ActorRefFromLogic
export type ActorRefFrom<T> = ReturnTypeOrValue<T> extends infer R
? R extends StateMachine<
infer TContext,
infer TEvent,
infer TChildren,
infer _TActor,
infer _TAction,
infer _TGuard,
infer _TDelay,
infer TStateValue,
infer TTag,
infer _TInput,
infer TOutput,
infer TEmitted,
infer TMeta,
infer TStateSchema
>
? ActorRef<
MachineSnapshot<
TContext,
TEvent,
TChildren,
TStateValue,
TTag,
TOutput,
TMeta,
TStateSchema
>,
TEvent,
TEmitted
>
: R extends Promise<infer U>
? ActorRefFrom<PromiseActorLogic<U>>
: R extends ActorLogic<
infer TSnapshot,
infer TEvent,
infer _TInput,
infer _TSystem,
infer TEmitted
>
? ActorRef<TSnapshot, TEvent, TEmitted>
: never
? R extends AnyActorLogic
? ActorRefFromLogic<R>
: never
: never;

export type ActorRefFromLogic<T extends AnyActorLogic> = ActorRef<
SnapshotFrom<T>,
EventFromLogic<T>,
EmittedFrom<T>
>;

export type DevToolsAdapter = (service: AnyActor) => void;

/** @deprecated Use `Actor<T>` instead. */
Expand Down Expand Up @@ -2259,7 +2228,7 @@ export type AnyActorLogic = ActorLogic<
export type UnknownActorLogic = ActorLogic<
any, // snapshot
any, // event
never, // input
any, // input
AnyActorSystem,
any // emitted
>;
Expand Down Expand Up @@ -2429,7 +2398,9 @@ type ExtractLiteralString<T extends string | undefined> = T extends string
: never;

type ToConcreteChildren<TActor extends ProvidedActor> = {
[A in TActor as ExtractLiteralString<A['id']>]?: ActorRefFrom<A['logic']>;
[A in TActor as ExtractLiteralString<A['id']>]?: ActorRefFromLogic<
A['logic']
>;
};

export type ToChildren<TActor extends ProvidedActor> =
Expand All @@ -2444,7 +2415,7 @@ export type ToChildren<TActor extends ProvidedActor> =
{
include: {
[id: string]: TActor extends any
? ActorRefFrom<TActor['logic']> | undefined
? ActorRefFromLogic<TActor['logic']> | undefined
: never;
};
exclude: {};
Expand Down
21 changes: 12 additions & 9 deletions packages/core/test/actions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@ import {
raise,
sendParent,
sendTo,
spawnChild,
stopChild
} from '../src/actions.ts';
import { CallbackActorRef, fromCallback } from '../src/actors/callback.ts';
import {
ActorRef,
ActorRefFrom,
ActorRefFromLogic,
AnyActorRef,
EventObject,
Snapshot,
Expand Down Expand Up @@ -1551,7 +1550,7 @@ describe('entry/exit actions', () => {
const parent = createMachine({
types: {} as {
context: {
child: ActorRefFrom<typeof child>;
child: ActorRefFromLogic<typeof child>;
};
},
id: 'parent',
Expand Down Expand Up @@ -1596,7 +1595,7 @@ describe('entry/exit actions', () => {
const parent = createMachine({
types: {} as {
context: {
child: ActorRefFrom<typeof child>;
child: ActorRefFromLogic<typeof child>;
};
},
id: 'parent',
Expand Down Expand Up @@ -2976,7 +2975,7 @@ describe('sendTo', () => {
const parentMachine = createMachine({
types: {} as {
context: {
child: ActorRefFrom<typeof childMachine>;
child: ActorRefFromLogic<typeof childMachine>;
};
},
context: ({ spawn }) => ({
Expand Down Expand Up @@ -3008,7 +3007,7 @@ describe('sendTo', () => {
const parentMachine = createMachine({
types: {} as {
context: {
child: ActorRefFrom<typeof childMachine>;
child: ActorRefFromLogic<typeof childMachine>;
count: number;
};
},
Expand Down Expand Up @@ -3045,7 +3044,7 @@ describe('sendTo', () => {
createMachine({
types: {} as {
context: {
child: ActorRefFrom<typeof childMachine>;
child: ActorRefFromLogic<typeof childMachine>;
};
},
context: ({ spawn }) => ({
Expand Down Expand Up @@ -3076,7 +3075,9 @@ describe('sendTo', () => {
});

const parentMachine = createMachine({
types: {} as { context: { child: ActorRefFrom<typeof childMachine> } },
types: {} as {
context: { child: ActorRefFromLogic<typeof childMachine> };
},
context: ({ spawn }) => ({
child: spawn(childMachine, { id: 'child' })
}),
Expand Down Expand Up @@ -3105,7 +3106,9 @@ describe('sendTo', () => {
});

const parentMachine = createMachine({
types: {} as { context: { child: ActorRefFrom<typeof childMachine> } },
types: {} as {
context: { child: ActorRefFromLogic<typeof childMachine> };
},
context: ({ spawn }) => ({
child: spawn(childMachine)
}),
Expand Down
24 changes: 23 additions & 1 deletion packages/core/test/types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,13 @@ import {
stateIn,
setup,
toPromise,
UnknownActorRef
UnknownActorRef,
AnyActorLogic,
ActorRef,
SnapshotFrom,
EmittedFrom,
EventFrom,
ActorRefFromLogic
} from '../src/index';

function noop(_x: unknown) {
Expand Down Expand Up @@ -4582,3 +4588,19 @@ it('UnknownActorRef should return a Snapshot-typed value from getSnapshot()', ()
// @ts-expect-error
actor.getSnapshot().status === 'FOO';
});

it('Actor<T> should be assignable to ActorRefFromLogic<T>', () => {
const logic = createMachine({});

class ActorThing<T extends AnyActorLogic> {
actorRef: ActorRefFromLogic<T>;
constructor(actorLogic: T) {
const actor = createActor(actorLogic);

actor satisfies ActorRefFromLogic<typeof actorLogic>;
this.actorRef = actor;
}
}

new ActorThing(logic);
});

0 comments on commit a275d27

Please sign in to comment.