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

Conditional type inference regression in 4.2* #43192

Closed
robertf224 opened this issue Mar 11, 2021 · 4 comments
Closed

Conditional type inference regression in 4.2* #43192

robertf224 opened this issue Mar 11, 2021 · 4 comments
Assignees
Labels
Breaking Change Would introduce errors in existing code Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@robertf224
Copy link

robertf224 commented Mar 11, 2021

Bug Report

πŸ”Ž Search Terms

"conditional type inference"

πŸ•— Version & Regression Information

This changed between versions 4.1.5 and 4.2.2

⏯ Playground Link

Playground link with relevant code

πŸ’» Code

export interface IFruits {
    tropical: {
        papaya: number;
        pineapple: number;
        guava: number;
    };

    stone: {
        peach: number;
        nectarine: number;
    };

    berry: {
        strawberry: number;
        blueberry: number;
    };
}

/**
 * Aggregating all the second level keys (the fruits) into a single union type.
 */
export type FruitKey = {
    [category in keyof IFruits]: keyof IFruits[category];
}[keyof IFruits];

/**
 * Helper to pick only fruit categories (first level) that are required for the requested fruit keys.
 */
type RequiredFruitCategories<K extends FruitKey> = {
    [category in keyof IFruits]: Extract<K, keyof IFruits[category]> extends never ? never : category;
}[keyof IFruits];


/**
 * Takes a nested slice of the IFruits interface depending on the fruit keys (second level) that
 * we ask for -- essentially a nested `Pick`.  So `FruitsSubset<"papaya" | "peach">` ends up being:
 * {
 *      tropical: {
 *          papaya: number;
 *      };
 *      stone: {
 *          peach: number;
 *      };
 * }
 */
type FruitsSubset<K extends FruitKey> = {
    [category in RequiredFruitCategories<K>]: Pick<
        IFruits[category],
        Extract<K, keyof IFruits[category]>
    >;
};

/**
 * Just wrapping the above in an interface for other interfaces (i.e. React props) to extend.
 */
interface FruitsSubsetOpts<K extends FruitKey> {
    fruits: FruitsSubset<K>;
}

type MyFavoriteFruitsSubset = FruitsSubset<"papaya" | "peach">;
// Inferring the key from above -- this ends up being `"papaya" | "peach"` on TS 4.1.5 but `never` on 4.2.3
type MyFavoriteFruitsSubsetKey = MyFavoriteFruitsSubset extends FruitsSubset<infer K> ? K : never;

type MyFavoriteFruitsSubsetOpts = FruitsSubsetOpts<"papaya" | "peach">;
// Inferring the key from above -- this ends up being `"papaya" | "peach"` on both TS 4.1.5 and 4.2.3
// Pretty confusing because FruitsSubsetOpts is just a wrapper around FruitsSubset!
type MyFavoriteFruitsSubsetOptsKey = MyFavoriteFruitsSubsetOpts extends FruitsSubsetOpts<infer K> ? K : never;

πŸ™ Actual behavior

MyFavoriteFruitsSubsetKey can be properly inferred on TS 4.1.5 but not on 4.2.3 (we tried migrating to 4.2.2 locally and this broke so the change in behavior is somewhere between 4.1.5 and 4.2.2)

πŸ™‚ Expected behavior

MyFavoriteFruitsSubsetKey should be properly inferred on both versions. Could be we're doing a thing we shouldn't be doing in the first place, but it's strange that it only kind of breaks and doesn't completely break.

@MartinJohns
Copy link
Contributor

You forgot to fill out the issue template. πŸ€”

@robertf224
Copy link
Author

ah sorry will edit

@RyanCavanaugh RyanCavanaugh added the Needs Investigation This issue needs a team member to investigate its status. label Mar 11, 2021
@RyanCavanaugh RyanCavanaugh added this to the TypeScript 4.3.1 milestone Mar 11, 2021
@ahejlsberg
Copy link
Member

This is an effect of #42284 and basically a duplicate of #42421.

Previously, the MyFavoriteFruitsSubset type alias would resolve to the exact same type identity as FruitsSubset<"papaya" | "peach">, and when printing back types in diagnostics and quick info, that's what we'd show. Now, we create a separate type identity for MyFavoriteFruitsSubset and preserve the alias when printing back the type. In the MyFavoriteFruitsSubsetKey conditional type this means we're no longer inferring from one instantiation of the FruitsSubset<...> type alias to another (where we can just infer between the type arguments). Rather, we're performing structural type inference between two mapped types, and, because the transformation is lossy, we can't produce an inference for K.

@ahejlsberg ahejlsberg added Breaking Change Would introduce errors in existing code Working as Intended The behavior described is the intended behavior; this is not a bug and removed Needs Investigation This issue needs a team member to investigate its status. labels Mar 29, 2021
@ahejlsberg ahejlsberg removed this from the TypeScript 4.3.1 milestone Mar 29, 2021
@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
Breaking Change Would introduce errors in existing code Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

5 participants