Bug Report
🔎 Search Terms
inference, type parameters, conditional type, signature resolving
🕗 Version & Regression Information
This is the behavior present in all 4.x versions available on the playground, including 4.5-beta.
⏯ Playground Link
Slimmed down repro case
The originally reported TS playground
💻 Code
This is the code for the slimmed-down variant. Note that the createMachine2 is not a part of the repro case but it shows how putting the conditional type on a "different level" makes it work - which I find to be surprising because semantically both variants do the same thing
interfaceEventObject{type: string;}interfaceTypegenDisabled{"@@xstate/typegen": false;}interfaceTypegenEnabled{"@@xstate/typegen": true;}typeTypegenConstraint=TypegenEnabled|TypegenDisabled;interfaceActionObject<TEventextendsEventObject>{type: string;_TE?: TEvent;}declarefunctionassign<TEventextendsEventObject>(assignment: (ev: TEvent)=>void): ActionObject<TEvent>;declarefunctioncreateMachine<TTypesMetaextendsTypegenConstraint=TypegenDisabled>(config: {types?: TTypesMeta;},action?: TTypesMetaextendsTypegenEnabled
? {action: ActionObject<{type: "WITH_TYPEGEN"}>}
: {action: ActionObject<{type: "WITHOUT_TYPEGEN"}>}): void;createMachine({types: {}asTypegenEnabled,},{// `action` property is of a correct type - it expects `ActionObject<{ type: "WITH_TYPEGEN" }> }`action: assign((event)=>{// but the type of this `assign` has been inferred to `ActionObject<{ type: "WITHOUT_TYPEGEN" } | { type: "WITH_TYPEGEN" }>`// so `event` here is of type `{ type: "WITHOUT_TYPEGEN" } | { type: "WITH_TYPEGEN" }`((_accept: "WITH_TYPEGEN")=>{})(event.type);}),});// below we have the code that is not part of the repro case but which is semantically the same as the one above and yet it behaves differentlydeclarefunctioncreateMachine2<TTypesMetaextendsTypegenConstraint=TypegenDisabled>(config: {types?: TTypesMeta;},action?: {// it works correctly if we check the `TTypesMeta` further down the roadaction: TTypesMetaextendsTypegenEnabled
? ActionObject<{type: "WITH_TYPEGEN"}>
: ActionObject<{type: "WITHOUT_TYPEGEN"}>;}): void;createMachine2({types: {}asTypegenEnabled,},{action: assign((event)=>{((_accept: "WITH_TYPEGEN")=>{})(event.type);}),});The code below showcases more accurately what I'm trying to achieve.
Code from the originally reported playground
typeCast<Textendsany,TCastTypeextendsany>=TextendsTCastType
? T
: TCastType;typeProp<T,K>=KextendskeyofT ? T[K] : never;typeIndexByType<Textends{type: string}>={[KinT["type"]]: Extract<T,{type: K}>;};interfaceEventObject{type: string;}interfaceTypegenDisabled{"@@xstate/typegen": false;}interfaceTypegenEnabled{"@@xstate/typegen": true;}typeTypegenConstraint=TypegenEnabled|TypegenDisabled;typeResolveTypegenMeta<TTypesMetaextendsTypegenConstraint,TEventextends{type: string}>=TTypesMetaextendsTypegenEnabled
? TTypesMeta&{indexedEvents: IndexByType<TEvent>;}
: TypegenDisabled;interfaceActionObject<TEventextendsEventObject>{type: string;_TE?: TEvent;}interfaceMachineOptions<TEventextendsEventObject>{actions?: {[name: string]: ActionObject<TEvent>;};}typeTypegenMachineOptions<TResolvedTypesMeta,TEventsCausingActions=Prop<TResolvedTypesMeta,"eventsCausingActions">,TIndexedEvents=Prop<TResolvedTypesMeta,"indexedEvents">>={actions?: {[KinkeyofTEventsCausingActions]?: ActionObject<Cast<Prop<TIndexedEvents,TEventsCausingActions[K]>,EventObject>>;};};typeMaybeTypegenMachineOptions<TEventextendsEventObject,TResolvedTypesMeta=TypegenDisabled>=TResolvedTypesMetaextendsTypegenEnabled
? TypegenMachineOptions<TResolvedTypesMeta>
: MachineOptions<TEvent>;declarefunctionassign<TEventextendsEventObject>(assignment: (ev: TEvent)=>void): ActionObject<TEvent>;declarefunctioncreateMachine<TEventextends{type: string},TTypesMetaextendsTypegenConstraint=TypegenDisabled,>(config: {schema?: {events?: TEvent;};types?: TTypesMeta;},options?: MaybeTypegenMachineOptions<TEvent,ResolveTypegenMeta<TTypesMeta,TEvent>>): void;interfaceTypesMetaextendsTypegenEnabled{eventsCausingActions: {actionName: "BAR";};}createMachine({types: {}asTypesMeta,schema: {events: {}as{type: "FOO"}|{type: "BAR";value: string},},},{actions: {// `event` here is not narrowed down to the BAR one// yet the `actionName`'s type (available when I hover over the property) is correctactionName: assign((event)=>{((_accept: "BAR")=>{})(event.type);}),},});🙁 Actual behavior
event parameter here is not narrowed down to the WITH_TYPEGEN event, yet the actionName's type (available when I hover over the property) is "correct" (only WITH_TYPEGEN event there)
🙂 Expected behavior
I would expect this event parameter to be inferred correctly.
I've been debugging this for a bit and I think this is roughly what happens:
- we have nested
chooseOverload+inferTypeArguments calls because assign happens "within" createMachine typeParameters for assign are assigned based on both branches of the conditional type - basically, a union of both is created for this position and since there is an overlap the reduced type gets assigned there.- the "routing" conditional type gets instantiated only after we exit the outer
inferTypeArguments and it happens within getSignatureApplicabilityError but the assign has been instantiated and cached a step back - within inferTypeArguments - the inferred type for
assign is never rechecked - so it stays as if it was supposed to handle both branches of the conditional type
Note that this might be highly incorrect as I'm not too familiar with the codebase
Bug Report
🔎 Search Terms
inference, type parameters, conditional type, signature resolving
🕗 Version & Regression Information
This is the behavior present in all 4.x versions available on the playground, including 4.5-beta.
⏯ Playground Link
Slimmed down repro case
The originally reported TS playground
💻 Code
This is the code for the slimmed-down variant. Note that the
createMachine2is not a part of the repro case but it shows how putting the conditional type on a "different level" makes it work - which I find to be surprising because semantically both variants do the same thingThe code below showcases more accurately what I'm trying to achieve.
Code from the originally reported playground
🙁 Actual behavior
eventparameter here is not narrowed down to the WITH_TYPEGEN event, yet theactionName's type (available when I hover over the property) is "correct" (only WITH_TYPEGEN event there)🙂 Expected behavior
I would expect this
eventparameter to be inferred correctly.I've been debugging this for a bit and I think this is roughly what happens:
chooseOverload+inferTypeArgumentscalls becauseassignhappens "within"createMachinetypeParametersforassignare assigned based on both branches of the conditional type - basically, a union of both is created for this position and since there is an overlap the reduced type gets assigned there.inferTypeArgumentsand it happens withingetSignatureApplicabilityErrorbut theassignhas been instantiated and cached a step back - withininferTypeArgumentsassignis never rechecked - so it stays as if it was supposed to handle both branches of the conditional typeNote that this might be highly incorrect as I'm not too familiar with the codebase