Skip to content

Aliasing a type in a conditional type changes the result #48936

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

Closed
marcj opened this issue May 3, 2022 · 5 comments
Closed

Aliasing a type in a conditional type changes the result #48936

marcj opened this issue May 3, 2022 · 5 comments
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed

Comments

@marcj
Copy link

marcj commented May 3, 2022

Bug Report

πŸ”Ž Search Terms

any, alias, conditional type,

πŸ•— Version & Regression Information

All versions are effected.

⏯ Playground Link

Playground Link

πŸ’» Code

interface User {
    id: number;
    username: string;
}

type Q<T> = {a: keyof T & string };

type t1 = Q<any> extends Q<User> ? true : never;

type qAny = Q<any>;
type t2 = qAny extends Q<User> ? true : never;

πŸ™ Actual behavior

t1 is true, and t2 is never.

πŸ™‚ Expected behavior

Both, t1 and t2 should be the same and be never, because {a: string} extends {a: 'id' | 'username'} is false.
The only difference is that the left value of extends is for t2 an alias, but the same type as in t1.

Interesting might be that t2 was in v4.1 true as well, but started to get the right value in v4.2.

Maybe related to #31295

@tjjfvi
Copy link
Contributor

tjjfvi commented May 3, 2022

I believe this boils down to an unsound variance check. TS detects Q<T> as contravariant, since keyof is contravariant, and thus simplifies the check of Q<any> extends Q<User> to User extends any, which is obviously true. However, keyof is not always contravariant; for example:

type A = { x: 1, y?: 2 }
type B = { x: 1 }

const p: A = null! as B; // ok, B is assignable to A
const q: keyof B = null! as keyof A; // error, keyof A is not assignable to keyof B

type Q<T> = { x: keyof T };
type T1 = Q<A> extends Q<B> ? true : never; // true
type QA = Q<A>;
type T2 = QA extends Q<B> ? true : never; // never

T2 is never because the intermediate type alias causes the instantiation of Q<A>, making the extends use a structural comparison instead of the unsound variance comparison.

@MartinJohns
Copy link
Contributor

Possibly a duplicate of #48070?

@andrewbranch
Copy link
Member

IIUC #48070, or at least the draft fix that Anders put up, is specific to signatures. I think @tjjfvi nailed it. @ahejlsberg thoughts / triage help?

@ahejlsberg
Copy link
Member

Yes, @tjjfvi's analysis is correct. When relating two instantiations of the same generic type, we rely on variance the measured variance for that generic type. However, when an instantiation of some generic type is re-aliased (as in the qAny and QA cases above), we'll relate the types structurally.

The "fix" here would be to mark variance of types involving keyof T as unreliable or unmeasurable, but we know doing so comes with a heavy performance toll in some scenarios. So we'd trade one problem for another.

@andrewbranch andrewbranch added the Design Limitation Constraints of the existing architecture prevent this from being fixed label May 4, 2022
@marcj
Copy link
Author

marcj commented May 5, 2022

@ahejlsberg Thanks. If this is a design limitation of the implementation of the type checker, is it safe to say that the outlined expectations are valid and this will be fixed in the future? I'm asking because I reimplement the TypeScript type computation (see #47658) and I have to choose what behaviour I will implement. I could certainly copy this arguably unexpected inconsistency but would prefer to keep it clean.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed
Projects
None yet
Development

No branches or pull requests

6 participants