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

some types in 4.5.0 aren't correctly resolving to same types in 4.4.4 #46624

Closed
suneettipirneni opened this issue Nov 1, 2021 · 8 comments · Fixed by #46645
Closed

some types in 4.5.0 aren't correctly resolving to same types in 4.4.4 #46624

suneettipirneni opened this issue Nov 1, 2021 · 8 comments · Fixed by #46645
Assignees
Labels
Bug A bug in TypeScript Fix Available A PR has been opened for this issue Recent Regression This is a new regression just found in the last major/minor version of TypeScript.

Comments

@suneettipirneni
Copy link

suneettipirneni commented Nov 1, 2021

Bug Report

I recently ran into the this strange error when I updated to typescript 4.5.0-beta, generic types that were resolving properly in 4.4.4, are not correctly resolving in 4.5.0-beta. Instead 4.5.0 returns a unions of all of the possible values while the same code in 4.4.4 returns a narrowed generic type.

🔎 Search Terms

4.5.0-beta, unions, generics

🕗 Version & Regression Information

typescript@beta, 4.5.0-beta, typescript@next

  • This changed between versions 4.4.4 and 4.5.0-beta

⏯ Playground Link

Same code for both versions:

Playground link with relevant code

💻 Code

export type kind = "one" | "two" | "three";

export interface Options<T> {
  item?: T;
  type?: kind;
}

type One = {};
type Two = {};
type Three = {};

type OneOptions = Options<One>;
type TwoOptions = Options<Two>;
type ThreeOption = Options<Three>;

interface MappedInterfaceOptions {
    "one": OneOptions;
    "two": TwoOptions;
    "three": ThreeOption;
}

export type InterfaceExtractor<
  T extends kind,
> = T extends kind
  ? MappedInterfaceOptions[T] extends Options<infer Item>
    ? Item
    : never
  : Options<One>;

export type Params<T extends kind> = { type?: T; } & Options<InterfaceExtractor<T>>;

declare function getInterfaceFromString<
  T extends kind,
>(options?: Params<T>): InterfaceExtractor<T>;

// Typescript 4.4.4 states the type is `Two`, 4.5.0 states the type is `One` | `Two` | `Three`
const result = getInterfaceFromString({ type: 'two' });

🙁 Actual behavior

In typescript 4.4.4 the type is correctly resolved to Two, whereas in typescript 4.5.0 the type is resolved to One | Two | Three which is incorrect.
4.4.4:
Screen Shot 2021-11-01 at 1 27 03 PM
4.5.0-beta:
Screen Shot 2021-11-01 at 1 27 22 PM

🙂 Expected behavior

The correct type for result in the playground and the codesample is Two, not One | Two | Three, typescript 4.5.0 should resolve to this type.

@suneettipirneni
Copy link
Author

suneettipirneni commented Nov 1, 2021

it seems that this line causes the discrepancy:

export type Params<T extends kind> = { type?: T } & Options<T>;

Changing this to:

export type Params<T extends kind> = { type?: T };

Resolves both versions to the same type: One | Two | Three, however I'm not sure why the original typedef causes issues on 4.5.0

@andrewbranch
Copy link
Member

The issue is we're seeing

const result = getInterfaceFromString({ type: 'two' });
//                                    { type?: kind }

instead of

const result = getInterfaceFromString({ type: 'two' });
//                                    { type?: "two" }

Using getInterfaceFromString({ type: 'two' } as const) gets the result you expect. Looks like a contextual typing regression.

@andrewbranch andrewbranch added Bug A bug in TypeScript Recent Regression This is a new regression just found in the last major/minor version of TypeScript. labels Nov 1, 2021
@andrewbranch
Copy link
Member

Unclear if fixing this in 4.5 will be possible without other breaking changes, but worth looking into.

@sandersn
Copy link
Member

sandersn commented Nov 2, 2021

Note that both 4.5 and 4.4 say that the type is Two from tsc.

@sandersn
Copy link
Member

sandersn commented Nov 2, 2021

Broken by #46052

@sandersn sandersn assigned ahejlsberg and unassigned sandersn Nov 2, 2021
@andrewbranch
Copy link
Member

The surprising thing is that exactOptionalPropertyTypes isn't even enabled in this repro. @ahejlsberg any ideas?

@ahejlsberg
Copy link
Member

Ooo, this is a nasty little issue. During union type construction we repurpose certain TypeFlags to gather data about what kinds of types are in the union. But we then have a test for whether a union should be marked primitive-types-only, and that test erroneously assumes the original meanings of the TypeFlags. It broke because we moved some of the repurposed flags around. I have a fix for it, will put up a PR shortly. If possible, we should take this for 4.5.1.

@ahejlsberg
Copy link
Member

Here's a simplified repro:

export type Kind = "one" | "two" | "three";

declare function getInterfaceFromString<T extends Kind>(options?: { type?: T } & { type?: Kind }): T;

const result = getInterfaceFromString({ type: 'two' });  // Should be "two", not Kind

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Fix Available A PR has been opened for this issue Recent Regression This is a new regression just found in the last major/minor version of TypeScript.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants