microsoft / microsoft/TypeScript

Intersected type variable with a recursive constraint fails to infer

Open
#55,645 4 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Needs Proposal Suggestion
Dominant language
Go
Stars
111k
Forks
14.3k
Avg merge
1d 19h
Merged PRs (30d)
117

Description

🔎 Search Terms

intersection type variable parameter inference recursive constraint

🕗 Version & Regression Information
  • This is the behavior in every version I tried
⏯ Playground Link

https://www.typescriptlang.org/play?ts=5.3.0-dev.20230906#code/C4TwDgpgBAysCGwIGED2A7AZgSwOYB4AVAQQGNhsMoIAPJdAEwGcongAnbdXAPigF4oAbwBQUaug4gA-AC4oJcpXRjWCJEzlQAShFKp2DfG07cANLHUoMOAoooYePANwiAvq5GhIlxBBikABYQALbwAsKqbH6a8rr6hsYcXLgWcH4BwWEu7p4MegA28OzQmACu6EpUpCV+ALLwQVwQ+Kr2ytR0EIwsJilmbWhYeJ30zL5ImaHwAzwAFPrDuPKEQ7ZQAGQT1ktEZA7oPACU8gDa7RgWqzZ4ALqei2xQXJgQ7CUMAArF8CEsgjUIPVGoFmnNROJulJ5AAiTCoVAwgbiaIaeQQ8RQeDo1SYiTQqAwgBGxSRuKgbmRFIGbiOnhebw+33YvyYrgA9OyoAA9aQiIA

💻 Code
type StateConfig<TAction extends string> = {
  entry?: TAction
  states?: Record<string, StateConfig<TAction>>;
};

type StateSchema = {
  states?: Record<string, StateSchema>;
};

declare function createMachine<
  TAction extends string,
  TConfig extends StateSchema,
>(config: TConfig & StateConfig<TAction>): [TAction, TConfig];

const inferredParams = createMachine({
  entry: "foo",
  states: {
    a: {
      entry: "bar",
    },
  },
});

inferredParams; // ["foo" | "bar", StateSchema]
🙁 Actual behavior

TConfig gets inferred as its constraint

🙂 Expected behavior

I'd expect TConfig to infer successfully

Additional information about the issue

This is very related to the other issue I just created (see here). The underlying problem of this particular example is the same. The inferredType for TConfig fails the compareTypes check in getInferredType here. This time there is no excess property errors apparent to the user because the excess properties are satisfied through the intersection.

Despite the problem having the same root cause I think it's worth discussing this separately. The other issue could be closed as design limitation/working as intended but this one feels to me a lot closer to being an actual bug that would be worth fixing.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the inference result from the provided TypeScript Playground link, then read src/compiler/checker.ts around getInferredType and its compareTypes check. Trace why the intersected recursive constraint causes TConfig to fall back to StateSchema; done means the example infers the configuration successfully without regressing related inference behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
compilers
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.