Skip to content

Contextual generic return type of an async function includes union members from the context that it shouldn'tΒ #47682

Closed
@Andarist

Description

@Andarist

Bug Report

πŸ”Ž Search Terms

contextual typing, generic, async, return type, union

πŸ•— Version & Regression Information

This is the behavior in every version I tried, and I reviewed the FAQ

⏯ Playground Link

TS playground (latest)
TS playground (nightly)

πŸ’» Code

declare class StateMachine<T> {
  onDone: (a: T) => void;
}

declare function createMachine<T>(implementations: {
  services: Record<string, () => Promise<T> | StateMachine<T>>;
}): void;

// this is close to my original issue found in the real-world in XState
createMachine<{ count: number }>({
  services: {
    test: async () => Promise.reject("some err"),
    async test2() {
      return Promise.reject("some err");
    },
  },
});

// this is an additional test case I've figured out that behaves in a similar way.
function fn1(): () => Promise<{ count: number }> | StateMachine<{ count: number }> {
  return async () => Promise.reject('some err')
}

πŸ™ Actual behavior

Both tests fail to compile and the reported error looks bizzare(-ish):

Type '() => Promise<{ count: number; } | StateMachine<{ count: number; }>>' is not assignable to type '() => Promise<{ count: number; }> | StateMachine<{ count: number; }>'.
  Type 'Promise<{ count: number; } | StateMachine<{ count: number; }>>' is not assignable to type 'Promise<{ count: number; }> | StateMachine<{ count: number; }>'.
    Type 'Promise<{ count: number; } | StateMachine<{ count: number; }>>' is not assignable to type 'Promise<{ count: number; }>'

For some reason, the return type here didn't narrow the contextual union to include only Promise(-like?) members and dragged the whole Awaited<Union> to the computed contextual type. And this has caused an assignability problem.

Note that this "only" happens with Promise.reject, it's not reproducible with Promise.resolve and other similar types. My guess is that it's because Promise.reject contains a generic only at the return type position and this is what makes this issue manifest somehow.

πŸ™‚ Expected behavior

No error should be reported because this is valid code and it looks like sufficient information has been provided in the context for this to be narrowed down properly~.


While looking into this problem I've poked around the internals of TS and figured out that this might be a potential fix for this issue: #47683

Metadata

Metadata

Assignees

Labels

Fix AvailableA PR has been opened for this issueNeeds InvestigationThis issue needs a team member to investigate its status.

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions