microsoft / microsoft/TypeScript

Incorrect widening (constraint fallback?) during inference when union with `undefined` appears in constraint

Open
#44,891 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Bug Domain: check: Type Inference
Dominant language
Go
Stars
111k
Forks
14.4k
Avg merge
1d 19h
Merged PRs (30d)
117

Description

Bug Report

🔎 Search Terms

union, type inference, imprecise union generic type, generic type.

🕗 Version & Regression Information

This is reproducible on all versions of the playground.

⏯ Playground Link

Playground link with relevant code

💻 Code
export interface Something<
  R extends
    | {
        [K in 'foo' | 'bar' | 'baz']?: {
          r?: unknown;
          p?: unknown;
        };
      }
    | undefined = undefined
> {}

const o: Something<{ foo: { r: 1; p: 2 }; bar: { r: 3 } }> = {};

declare function something<M extends T extends Something<infer R> ? keyof R : never, T extends Something>(
  key: M,
  something: T
): M;

declare function somethingBugged<M extends T extends Something<infer R> ? keyof R : never, T extends Something>(
  key: M,
  something: T
): [M];

const normal = something('foo', o);
// got: "foo"
// expected: "foo"

const bugged = somethingBugged('foo', o);
// got: ["foo" | "bar"]
// expected: ["foo"]

// However with `as const`, the correct type is returned.
const ugly = somethingBugged('foo' as const, o);
// got: ["foo"]
// expected: ["foo"]
🙁 Actual behavior

The type loses its precision and is the union instead of the exact type.

🙂 Expected behavior

The type is exact.


What is strange is that the generic type M loses precision ("foo" -> "foo" | "bar") with the somethingBugged function which returns a tuple of M (and it does the same thing with any other "wrapper", such as an object).
Whereas with the something function there is no problem. Both functions are identical.

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

Start with the linked TypeScript Playground example and compare inference for something and somethingBugged, especially when the inferred type is wrapped in a tuple or object. Investigate why M widens from "foo" to "foo" | "bar" only in the wrapped return type. Done means the wrapped form preserves the exact "foo" type without requiring as const.

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
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.