Skip to content
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

Generic inference incorrectly chooses Narrowest of two binding points #53433

Closed
cefn opened this issue Mar 22, 2023 · 2 comments
Closed

Generic inference incorrectly chooses Narrowest of two binding points #53433

cefn opened this issue Mar 22, 2023 · 2 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@cefn
Copy link

cefn commented Mar 22, 2023

Bug Report

Generic function type inference unnecessarily narrow in the case of two Generic bind sites, (object keys and array members) creating an unexpected error.

🔎 Search Terms

Generic function type narrow

🕗 Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about Generic inference and binding

⏯ Playground Link

Playground link with relevant code

💻 Code

type Wide = "vanilla" | "chocolate" | "raspberry";

type Lookup<Narrow extends Wide> = {
  [k in Narrow]: boolean;
};

function flag<Narrow extends Wide>(
  lookup: Lookup<Narrow>,
  ...items: [Narrow, ...Narrow[]]
) {
  for (const item of items) {
    lookup[item] = true;
  }
}

flag(
  {
    vanilla: false,
    chocolate: true, // this line is unexpectedly an error
  },
  "vanilla"
);

🙁 Actual behavior

The Narrow type in the call to flag is inferred only as "vanilla" (apparently taking account only of items and not inferring from lookup).

This causes the "chocolate" property in the lookup to be incorrectly flagged as an error, when the "chocolate" property should actually have caused the Narrow type to be broadened to include "chocolate".

This remains true if as const or a const Generic type is used for the lookup.

It's only if every single entry of keyof lookup is listed in items that the compiler errors go away, which is surprising, as the definition of items should allow one or more of the keys of lookup, but not necessarily all of them.

🙂 Expected behavior

A Narrow type of "vanilla" | "chocolate" should have been inferred from the lookup argument to the flag call. The type inferred from the lookup is a superset of the type inferred from the items therefore this should have been used instead, hence eliminating a spurious error.

@ahejlsberg
Copy link
Member

This is working as intended. The inferences we make from the top-level occurrences of the type parameter in the rest argument list are considered better inferences than those we make from the mapped type.

Ideally you want to indicate that no inferences should be made from the rest arguments, so this is effectively another request for #14829. However, the suggested workaround for that issue actually applies here:

type Wide = "vanilla" | "chocolate" | "raspberry";

function flag<T extends Wide, U extends T>(lookup: Record<T, boolean>, ...items: [U, ...U[]]) {
  for (const item of items) {
    lookup[item] = true;
  }
}

flag({ vanilla: false, chocolate: true }, "vanilla");  // Ok

@ahejlsberg ahejlsberg added the Working as Intended The behavior described is the intended behavior; this is not a bug label Mar 22, 2023
@typescript-bot
Copy link
Collaborator

This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

3 participants