Skip to content

Instantiate conditional types within contextual type instantiation when inference candidates are available #48838

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

Draft
wants to merge 14 commits into
base: main
Choose a base branch
from
Draft
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
6 changes: 5 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30752,7 +30752,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// If no inferences have been made, and none of the type parameters for which we are inferring
// specify default types, nothing is gained from instantiating as type parameters would just be
// replaced with their constraints similar to the apparent type.
if (inferenceContext && contextFlags! & ContextFlags.Signature && some(inferenceContext.inferences, hasInferenceCandidatesOrDefault)) {
if (
inferenceContext &&
(contextFlags! & ContextFlags.Signature || maybeTypeOfKind(contextualType, TypeFlags.Conditional)) &&
some(inferenceContext.inferences, hasInferenceCandidatesOrDefault)
) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

It turns out that this breaks one new test case (merged quite recently and related to a recent change, see change here). It can be fixed with:

diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index 5b1cf22c29..b3f719a1ca 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -30759,7 +30759,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
             ) {
                 // For contextual signatures we incorporate all inferences made so far, e.g. from return
                 // types as well as arguments to the left in a function call.
-                return instantiateInstantiableTypes(contextualType, inferenceContext.nonFixingMapper);
+                const instantiated = instantiateInstantiableTypes(contextualType, inferenceContext.nonFixingMapper);
+                if (contextFlags! & ContextFlags.Signature) {
+                    return instantiated;
+                }
+                return getIntersectionType([contextualType, instantiated]);
             }
             if (inferenceContext?.returnMapper) {
                 // For other purposes (e.g. determining whether to produce literal types) we only

However, I kinda feel that this particular potential fix would be better put into #56953

it's related to indexed access and my very initial goal behind this PR here 2 years ago was to introduce this change for conditional types. Only later on I realized that both belong to the Simplifiable group and I extended this PR. So one possible course of action would also be to revert support for IndexedAccess here for now and revisit this later.

I also don't think that this particular broken test case is that important. It was kind of created artificially~ by @jfet97 and me. It was just an attempt to make certain things work - and not necessarily a way that we wanted to write types with. That doesn't exactly mean that this is bad per se but perhaps it doesn't have to be fixed immediately. It didn't work in TS 5.3 anyway (see this TS playground where the reverse mapped types are not inferred).

This patch from the above breaks only one case, the one introduced in #53647 . The break is that it stops infer tuples so perhaps I will be able to find a simple fix later for it by adjusting inTupleContext.

In the meantime though, I'd love to learn what impact this PR has on the community tests etc in its current form.

Copy link
Contributor

Choose a reason for hiding this comment

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

The broken test case from #55811 could be easily fixed by using a const generic or with an as const assertion, therefore it doesn't seem like a big deal to me either.

// For contextual signatures we incorporate all inferences made so far, e.g. from return
// types as well as arguments to the left in a function call.
return instantiateInstantiableTypes(contextualType, inferenceContext.nonFixingMapper);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
//// [tests/cases/compiler/contextualInnerCallFromConditionalContextualType.ts] ////

=== contextualInnerCallFromConditionalContextualType.ts ===
interface EventObject { type: string; }
>EventObject : Symbol(EventObject, Decl(contextualInnerCallFromConditionalContextualType.ts, 0, 0))
>type : Symbol(EventObject.type, Decl(contextualInnerCallFromConditionalContextualType.ts, 0, 23))

interface TypegenDisabled { "@@xstate/typegen": false; }
>TypegenDisabled : Symbol(TypegenDisabled, Decl(contextualInnerCallFromConditionalContextualType.ts, 0, 39))
>"@@xstate/typegen" : Symbol(TypegenDisabled["@@xstate/typegen"], Decl(contextualInnerCallFromConditionalContextualType.ts, 1, 27))

interface TypegenEnabled { "@@xstate/typegen": true; }
>TypegenEnabled : Symbol(TypegenEnabled, Decl(contextualInnerCallFromConditionalContextualType.ts, 1, 56))
>"@@xstate/typegen" : Symbol(TypegenEnabled["@@xstate/typegen"], Decl(contextualInnerCallFromConditionalContextualType.ts, 2, 26))

type TypegenConstraint = TypegenEnabled | TypegenDisabled;
>TypegenConstraint : Symbol(TypegenConstraint, Decl(contextualInnerCallFromConditionalContextualType.ts, 2, 54))
>TypegenEnabled : Symbol(TypegenEnabled, Decl(contextualInnerCallFromConditionalContextualType.ts, 1, 56))
>TypegenDisabled : Symbol(TypegenDisabled, Decl(contextualInnerCallFromConditionalContextualType.ts, 0, 39))

interface ActionObject<TEvent extends EventObject> {
>ActionObject : Symbol(ActionObject, Decl(contextualInnerCallFromConditionalContextualType.ts, 4, 58))
>TEvent : Symbol(TEvent, Decl(contextualInnerCallFromConditionalContextualType.ts, 6, 23))
>EventObject : Symbol(EventObject, Decl(contextualInnerCallFromConditionalContextualType.ts, 0, 0))

type: string;
>type : Symbol(ActionObject.type, Decl(contextualInnerCallFromConditionalContextualType.ts, 6, 52))

_TE?: TEvent;
>_TE : Symbol(ActionObject._TE, Decl(contextualInnerCallFromConditionalContextualType.ts, 7, 15))
>TEvent : Symbol(TEvent, Decl(contextualInnerCallFromConditionalContextualType.ts, 6, 23))
}

declare function assign<TEvent extends EventObject>(
>assign : Symbol(assign, Decl(contextualInnerCallFromConditionalContextualType.ts, 9, 1))
>TEvent : Symbol(TEvent, Decl(contextualInnerCallFromConditionalContextualType.ts, 11, 24))
>EventObject : Symbol(EventObject, Decl(contextualInnerCallFromConditionalContextualType.ts, 0, 0))

assignment: (ev: TEvent) => void
>assignment : Symbol(assignment, Decl(contextualInnerCallFromConditionalContextualType.ts, 11, 52))
>ev : Symbol(ev, Decl(contextualInnerCallFromConditionalContextualType.ts, 12, 15))
>TEvent : Symbol(TEvent, Decl(contextualInnerCallFromConditionalContextualType.ts, 11, 24))

): ActionObject<TEvent>;
>ActionObject : Symbol(ActionObject, Decl(contextualInnerCallFromConditionalContextualType.ts, 4, 58))
>TEvent : Symbol(TEvent, Decl(contextualInnerCallFromConditionalContextualType.ts, 11, 24))

declare function createMachine<
>createMachine : Symbol(createMachine, Decl(contextualInnerCallFromConditionalContextualType.ts, 13, 24))

TTypesMeta extends TypegenConstraint = TypegenDisabled
>TTypesMeta : Symbol(TTypesMeta, Decl(contextualInnerCallFromConditionalContextualType.ts, 15, 31))
>TypegenConstraint : Symbol(TypegenConstraint, Decl(contextualInnerCallFromConditionalContextualType.ts, 2, 54))
>TypegenDisabled : Symbol(TypegenDisabled, Decl(contextualInnerCallFromConditionalContextualType.ts, 0, 39))

>(
config: {
>config : Symbol(config, Decl(contextualInnerCallFromConditionalContextualType.ts, 17, 2))

types?: TTypesMeta;
>types : Symbol(types, Decl(contextualInnerCallFromConditionalContextualType.ts, 18, 11))
>TTypesMeta : Symbol(TTypesMeta, Decl(contextualInnerCallFromConditionalContextualType.ts, 15, 31))

},
action?: TTypesMeta extends TypegenEnabled
>action : Symbol(action, Decl(contextualInnerCallFromConditionalContextualType.ts, 20, 4))
>TTypesMeta : Symbol(TTypesMeta, Decl(contextualInnerCallFromConditionalContextualType.ts, 15, 31))
>TypegenEnabled : Symbol(TypegenEnabled, Decl(contextualInnerCallFromConditionalContextualType.ts, 1, 56))

? { action: ActionObject<{ type: "WITH_TYPEGEN" }> }
>action : Symbol(action, Decl(contextualInnerCallFromConditionalContextualType.ts, 22, 7))
>ActionObject : Symbol(ActionObject, Decl(contextualInnerCallFromConditionalContextualType.ts, 4, 58))
>type : Symbol(type, Decl(contextualInnerCallFromConditionalContextualType.ts, 22, 30))

: { action: ActionObject<{ type: "WITHOUT_TYPEGEN" }> }
>action : Symbol(action, Decl(contextualInnerCallFromConditionalContextualType.ts, 23, 7))
>ActionObject : Symbol(ActionObject, Decl(contextualInnerCallFromConditionalContextualType.ts, 4, 58))
>type : Symbol(type, Decl(contextualInnerCallFromConditionalContextualType.ts, 23, 30))

): void;

createMachine(
>createMachine : Symbol(createMachine, Decl(contextualInnerCallFromConditionalContextualType.ts, 13, 24))
{
types: {} as TypegenEnabled,
>types : Symbol(types, Decl(contextualInnerCallFromConditionalContextualType.ts, 27, 3))
>TypegenEnabled : Symbol(TypegenEnabled, Decl(contextualInnerCallFromConditionalContextualType.ts, 1, 56))

},
{
action: assign((event) => {
>action : Symbol(action, Decl(contextualInnerCallFromConditionalContextualType.ts, 30, 3))
>assign : Symbol(assign, Decl(contextualInnerCallFromConditionalContextualType.ts, 9, 1))
>event : Symbol(event, Decl(contextualInnerCallFromConditionalContextualType.ts, 31, 20))

event.type // should be 'WITH_TYPEGEN'
>event.type : Symbol(type, Decl(contextualInnerCallFromConditionalContextualType.ts, 22, 30))
>event : Symbol(event, Decl(contextualInnerCallFromConditionalContextualType.ts, 31, 20))
>type : Symbol(type, Decl(contextualInnerCallFromConditionalContextualType.ts, 22, 30))

}),
}
);


Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
//// [tests/cases/compiler/contextualInnerCallFromConditionalContextualType.ts] ////

=== contextualInnerCallFromConditionalContextualType.ts ===
interface EventObject { type: string; }
>type : string

interface TypegenDisabled { "@@xstate/typegen": false; }
>"@@xstate/typegen" : false
>false : false

interface TypegenEnabled { "@@xstate/typegen": true; }
>"@@xstate/typegen" : true
>true : true

type TypegenConstraint = TypegenEnabled | TypegenDisabled;
>TypegenConstraint : TypegenDisabled | TypegenEnabled

interface ActionObject<TEvent extends EventObject> {
type: string;
>type : string

_TE?: TEvent;
>_TE : TEvent | undefined
}

declare function assign<TEvent extends EventObject>(
>assign : <TEvent extends EventObject>(assignment: (ev: TEvent) => void) => ActionObject<TEvent>

assignment: (ev: TEvent) => void
>assignment : (ev: TEvent) => void
>ev : TEvent

): ActionObject<TEvent>;

declare function createMachine<
>createMachine : <TTypesMeta extends TypegenConstraint = TypegenDisabled>(config: { types?: TTypesMeta;}, action?: TTypesMeta extends TypegenEnabled ? { action: ActionObject<{ type: "WITH_TYPEGEN"; }>;} : { action: ActionObject<{ type: "WITHOUT_TYPEGEN"; }>;}) => void

TTypesMeta extends TypegenConstraint = TypegenDisabled
>(
config: {
>config : { types?: TTypesMeta | undefined; }

types?: TTypesMeta;
>types : TTypesMeta | undefined

},
action?: TTypesMeta extends TypegenEnabled
>action : (TTypesMeta extends TypegenEnabled ? { action: ActionObject<{ type: "WITH_TYPEGEN";}>; } : { action: ActionObject<{ type: "WITHOUT_TYPEGEN";}>; }) | undefined

? { action: ActionObject<{ type: "WITH_TYPEGEN" }> }
>action : ActionObject<{ type: "WITH_TYPEGEN"; }>
>type : "WITH_TYPEGEN"

: { action: ActionObject<{ type: "WITHOUT_TYPEGEN" }> }
>action : ActionObject<{ type: "WITHOUT_TYPEGEN"; }>
>type : "WITHOUT_TYPEGEN"

): void;

createMachine(
>createMachine( { types: {} as TypegenEnabled, }, { action: assign((event) => { event.type // should be 'WITH_TYPEGEN' }), }) : void
>createMachine : <TTypesMeta extends TypegenConstraint = TypegenDisabled>(config: { types?: TTypesMeta | undefined; }, action?: (TTypesMeta extends TypegenEnabled ? { action: ActionObject<{ type: "WITH_TYPEGEN"; }>; } : { action: ActionObject<{ type: "WITHOUT_TYPEGEN"; }>; }) | undefined) => void
{
>{ types: {} as TypegenEnabled, } : { types: TypegenEnabled; }

types: {} as TypegenEnabled,
>types : TypegenEnabled
>{} as TypegenEnabled : TypegenEnabled
>{} : {}

},
{
>{ action: assign((event) => { event.type // should be 'WITH_TYPEGEN' }), } : { action: ActionObject<{ type: "WITH_TYPEGEN"; }>; }

action: assign((event) => {
>action : ActionObject<{ type: "WITH_TYPEGEN"; }>
>assign((event) => { event.type // should be 'WITH_TYPEGEN' }) : ActionObject<{ type: "WITH_TYPEGEN"; }>
>assign : <TEvent extends EventObject>(assignment: (ev: TEvent) => void) => ActionObject<TEvent>
>(event) => { event.type // should be 'WITH_TYPEGEN' } : (event: { type: "WITH_TYPEGEN"; }) => void
>event : { type: "WITH_TYPEGEN"; }

event.type // should be 'WITH_TYPEGEN'
>event.type : "WITH_TYPEGEN"
>event : { type: "WITH_TYPEGEN"; }
>type : "WITH_TYPEGEN"

}),
}
);


Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
//// [tests/cases/compiler/contextualInnerCallFromConditionalContextualType2.ts] ////

=== contextualInnerCallFromConditionalContextualType2.ts ===
interface ActorImpl {
>ActorImpl : Symbol(ActorImpl, Decl(contextualInnerCallFromConditionalContextualType2.ts, 0, 0))

src: string;
>src : Symbol(ActorImpl.src, Decl(contextualInnerCallFromConditionalContextualType2.ts, 0, 21))

output: any;
>output : Symbol(ActorImpl.output, Decl(contextualInnerCallFromConditionalContextualType2.ts, 1, 14))
}

interface DoneInvokeEvent<TData> {
>DoneInvokeEvent : Symbol(DoneInvokeEvent, Decl(contextualInnerCallFromConditionalContextualType2.ts, 3, 1))
>TData : Symbol(TData, Decl(contextualInnerCallFromConditionalContextualType2.ts, 5, 26))

type: `done.invoke.${string}`;
>type : Symbol(DoneInvokeEvent.type, Decl(contextualInnerCallFromConditionalContextualType2.ts, 5, 34))

output: TData;
>output : Symbol(DoneInvokeEvent.output, Decl(contextualInnerCallFromConditionalContextualType2.ts, 6, 32))
>TData : Symbol(TData, Decl(contextualInnerCallFromConditionalContextualType2.ts, 5, 26))
}

type AssignActionObject<TEvent extends { type: string }> = {
>AssignActionObject : Symbol(AssignActionObject, Decl(contextualInnerCallFromConditionalContextualType2.ts, 8, 1))
>TEvent : Symbol(TEvent, Decl(contextualInnerCallFromConditionalContextualType2.ts, 10, 24))
>type : Symbol(type, Decl(contextualInnerCallFromConditionalContextualType2.ts, 10, 40))

type: "xstate.assign";
>type : Symbol(type, Decl(contextualInnerCallFromConditionalContextualType2.ts, 10, 60))

ev: TEvent;
>ev : Symbol(ev, Decl(contextualInnerCallFromConditionalContextualType2.ts, 11, 24))
>TEvent : Symbol(TEvent, Decl(contextualInnerCallFromConditionalContextualType2.ts, 10, 24))

(ev: TEvent): void;
>ev : Symbol(ev, Decl(contextualInnerCallFromConditionalContextualType2.ts, 13, 3))
>TEvent : Symbol(TEvent, Decl(contextualInnerCallFromConditionalContextualType2.ts, 10, 24))

};

type ActionFunction<TEvent extends { type: string }> = (ev: TEvent) => void;
>ActionFunction : Symbol(ActionFunction, Decl(contextualInnerCallFromConditionalContextualType2.ts, 14, 2))
>TEvent : Symbol(TEvent, Decl(contextualInnerCallFromConditionalContextualType2.ts, 16, 20))
>type : Symbol(type, Decl(contextualInnerCallFromConditionalContextualType2.ts, 16, 36))
>ev : Symbol(ev, Decl(contextualInnerCallFromConditionalContextualType2.ts, 16, 56))
>TEvent : Symbol(TEvent, Decl(contextualInnerCallFromConditionalContextualType2.ts, 16, 20))

declare function assign<TEvent extends { type: string }>(
>assign : Symbol(assign, Decl(contextualInnerCallFromConditionalContextualType2.ts, 16, 76))
>TEvent : Symbol(TEvent, Decl(contextualInnerCallFromConditionalContextualType2.ts, 18, 24))
>type : Symbol(type, Decl(contextualInnerCallFromConditionalContextualType2.ts, 18, 40))

assigner: (ev: TEvent) => void
>assigner : Symbol(assigner, Decl(contextualInnerCallFromConditionalContextualType2.ts, 18, 57))
>ev : Symbol(ev, Decl(contextualInnerCallFromConditionalContextualType2.ts, 19, 13))
>TEvent : Symbol(TEvent, Decl(contextualInnerCallFromConditionalContextualType2.ts, 18, 24))

): AssignActionObject<TEvent>;
>AssignActionObject : Symbol(AssignActionObject, Decl(contextualInnerCallFromConditionalContextualType2.ts, 8, 1))
>TEvent : Symbol(TEvent, Decl(contextualInnerCallFromConditionalContextualType2.ts, 18, 24))

type Action<TEvent extends { type: string }> =
>Action : Symbol(Action, Decl(contextualInnerCallFromConditionalContextualType2.ts, 20, 30))
>TEvent : Symbol(TEvent, Decl(contextualInnerCallFromConditionalContextualType2.ts, 22, 12))
>type : Symbol(type, Decl(contextualInnerCallFromConditionalContextualType2.ts, 22, 28))

| AssignActionObject<TEvent>
>AssignActionObject : Symbol(AssignActionObject, Decl(contextualInnerCallFromConditionalContextualType2.ts, 8, 1))
>TEvent : Symbol(TEvent, Decl(contextualInnerCallFromConditionalContextualType2.ts, 22, 12))

| ActionFunction<TEvent>;
>ActionFunction : Symbol(ActionFunction, Decl(contextualInnerCallFromConditionalContextualType2.ts, 14, 2))
>TEvent : Symbol(TEvent, Decl(contextualInnerCallFromConditionalContextualType2.ts, 22, 12))

declare function createMachine<TActors extends ActorImpl>(config: {
>createMachine : Symbol(createMachine, Decl(contextualInnerCallFromConditionalContextualType2.ts, 24, 27))
>TActors : Symbol(TActors, Decl(contextualInnerCallFromConditionalContextualType2.ts, 26, 31))
>ActorImpl : Symbol(ActorImpl, Decl(contextualInnerCallFromConditionalContextualType2.ts, 0, 0))
>config : Symbol(config, Decl(contextualInnerCallFromConditionalContextualType2.ts, 26, 58))

types: {
>types : Symbol(types, Decl(contextualInnerCallFromConditionalContextualType2.ts, 26, 67))

actors: TActors;
>actors : Symbol(actors, Decl(contextualInnerCallFromConditionalContextualType2.ts, 27, 10))
>TActors : Symbol(TActors, Decl(contextualInnerCallFromConditionalContextualType2.ts, 26, 31))

};
states: Record<
>states : Symbol(states, Decl(contextualInnerCallFromConditionalContextualType2.ts, 29, 4))
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))

string,
{
invoke: TActors extends { src: infer TSrc }
>invoke : Symbol(invoke, Decl(contextualInnerCallFromConditionalContextualType2.ts, 32, 5))
>TActors : Symbol(TActors, Decl(contextualInnerCallFromConditionalContextualType2.ts, 26, 31))
>src : Symbol(src, Decl(contextualInnerCallFromConditionalContextualType2.ts, 33, 31))
>TSrc : Symbol(TSrc, Decl(contextualInnerCallFromConditionalContextualType2.ts, 33, 42))

? {
src: TSrc;
>src : Symbol(src, Decl(contextualInnerCallFromConditionalContextualType2.ts, 34, 11))
>TSrc : Symbol(TSrc, Decl(contextualInnerCallFromConditionalContextualType2.ts, 33, 42))

onDone: Action<DoneInvokeEvent<TActors["output"]>>;
>onDone : Symbol(onDone, Decl(contextualInnerCallFromConditionalContextualType2.ts, 35, 22))
>Action : Symbol(Action, Decl(contextualInnerCallFromConditionalContextualType2.ts, 20, 30))
>DoneInvokeEvent : Symbol(DoneInvokeEvent, Decl(contextualInnerCallFromConditionalContextualType2.ts, 3, 1))
>TActors : Symbol(TActors, Decl(contextualInnerCallFromConditionalContextualType2.ts, 26, 31))
}
: never;
}
>;
}): void;

createMachine({
>createMachine : Symbol(createMachine, Decl(contextualInnerCallFromConditionalContextualType2.ts, 24, 27))

types: {
>types : Symbol(types, Decl(contextualInnerCallFromConditionalContextualType2.ts, 43, 15))

actors: {} as {
>actors : Symbol(actors, Decl(contextualInnerCallFromConditionalContextualType2.ts, 44, 10))

src: "getRandomNumber";
>src : Symbol(src, Decl(contextualInnerCallFromConditionalContextualType2.ts, 45, 19))

output: { result: number };
>output : Symbol(output, Decl(contextualInnerCallFromConditionalContextualType2.ts, 46, 29))
>result : Symbol(result, Decl(contextualInnerCallFromConditionalContextualType2.ts, 47, 15))

},
},
states: {
>states : Symbol(states, Decl(contextualInnerCallFromConditionalContextualType2.ts, 49, 4))

a: {
>a : Symbol(a, Decl(contextualInnerCallFromConditionalContextualType2.ts, 50, 11))

invoke: {
>invoke : Symbol(invoke, Decl(contextualInnerCallFromConditionalContextualType2.ts, 51, 8))

src: "getRandomNumber",
>src : Symbol(src, Decl(contextualInnerCallFromConditionalContextualType2.ts, 52, 15))

onDone: assign((event) => {
>onDone : Symbol(onDone, Decl(contextualInnerCallFromConditionalContextualType2.ts, 53, 31))
>assign : Symbol(assign, Decl(contextualInnerCallFromConditionalContextualType2.ts, 16, 76))
>event : Symbol(event, Decl(contextualInnerCallFromConditionalContextualType2.ts, 54, 24))

event;
>event : Symbol(event, Decl(contextualInnerCallFromConditionalContextualType2.ts, 54, 24))

// ^?
}),
},
},
},
});

Loading