Skip to content

Covariance breaks when checking for undefinded in a type #55161

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

Open
AFatNiBBa opened this issue Jul 26, 2023 · 3 comments
Open

Covariance breaks when checking for undefinded in a type #55161

AFatNiBBa opened this issue Jul 26, 2023 · 3 comments
Assignees
Labels
Needs Investigation This issue needs a team member to investigate its status.

Comments

@AFatNiBBa
Copy link

AFatNiBBa commented Jul 26, 2023

Bug Report

I'm using the solid-js npm package which defines a type called Setter:

type Setter<T> = (undefined extends T ? () => undefined : {}) & (<U extends T>(value: (prev: T) => U) => U) & (<U extends T>(value: Exclude<U, Function>) => U) & (<U extends T>(value: Exclude<U, Function> | ((prev: T) => U)) => U);

I don't fully understand the reasoning behind it, but it's giving me some problems:

type Setter<T> = (undefined extends T ? () => undefined : {}) & (<U extends T>(value: (prev: T) => U) => U) & (<U extends T>(value: Exclude<U, Function>) => U) & (<U extends T>(value: Exclude<U, Function> | ((prev: T) => U)) => U);

class A { a = 1 }
class B extends A { b = 2 }

type Test<T> = { 0: Setter<T> };

declare const a: Test<A>;
const b: Test<B> = a; // ERROR!

I created an object with the property 0 which contains a Setter and it appears to not be covariant.
Inspecting the error it looks like that the problem is originated from this part undefined extends T ? () => undefined : {}, infact if I remove it this works, moreover the problem would still occur if that was the only part of the type:

type Setter<T> = undefined extends T ? () => undefined : {};

class A { a = 1 }
class B extends A { b = 2 }

type Test<T> = { 0: Setter<T> };

declare const a: Test<A>;
const b: Test<B> = a; // ERROR!

I think it is an error because the types are the same:

type TA = Test<A>;
//   ^? type TA = { 0: {}; }
type TB = Test<B>;
//   ^? type TB = { 0: {}; }

Additionally if Test were to be defined as [ Setter<T> ] the error would not occur:

type Setter<T> = undefined extends T ? () => undefined : {};

class A { a = 1 }
class B extends A { b = 2 }

type Test<T> = [ Setter<T> ];

declare const a: Test<A>;
const b: Test<B> = a; // OK?!?!

🔎 Search Terms

  • undefined extends T
  • covariance
  • covariant

🕗 Version & Regression Information

This changed between versions 3.3.3 and 3.5.1. (The only playground version in which it doesn't occur is 3.3.3)

⏯ Playground Link

Playground Link

💻 Code

type Setter<T> = (undefined extends T ? () => undefined : {}) & (<U extends T>(value: (prev: T) => U) => U) & (<U extends T>(value: Exclude<U, Function>) => U) & (<U extends T>(value: Exclude<U, Function> | ((prev: T) => U)) => U);
// type Setter<T> = (<U extends T>(value: (prev: T) => U) => U) & (<U extends T>(value: Exclude<U, Function>) => U) & (<U extends T>(value: Exclude<U, Function> | ((prev: T) => U)) => U);
// type Setter<T> = undefined extends T ? () => undefined : {};

class A { a = 1 }
class B extends A { b = 2 }

type Test<T> = { 0: Setter<T> };
// type Test<T> = [ Setter<T> ];

declare const a: Test<A>;
const b: Test<B> = a; // ERROR!

type TA = Test<A>;
//   ^?
type TB = Test<B>;
//   ^?

🙁 Actual behavior

Can't assign b with the value of a.
It is wrong at the very least because it is not consistent

🙂 Expected behavior

Can assign b with the value of a

@RyanCavanaugh RyanCavanaugh added Needs More Info The issue still hasn't been fully clarified and removed Needs More Info The issue still hasn't been fully clarified labels Jul 26, 2023
@RyanCavanaugh
Copy link
Member

RyanCavanaugh commented Jul 27, 2023

I believe the bug here is that we're not marking U extends T as being an unmeasurable variance computation on T.

// Measured as invariant (wrong)
type IsSupertypeOfString<T> = string extends T ? true : false;

// Covariant use of invariant type is still invariant
type BoxOfIsSupertypeOfString<T> = { readonly value: IsSupertypeOfString<T> };

declare let direct_number: IsSupertypeOfString<number>;
declare let direct_boolean: IsSupertypeOfString<boolean>;
// OK (non-variance-based relation)
direct_number = direct_boolean;

declare let box_number: BoxOfIsSupertypeOfString<number>;
declare let box_boolean: BoxOfIsSupertypeOfString<boolean>;
// Error (variance-based relation)
box_number = box_boolean;

cc @ahejlsberg

@RyanCavanaugh RyanCavanaugh added the Needs Investigation This issue needs a team member to investigate its status. label Jul 27, 2023
@weswigham
Copy link
Member

#43887 I thought I already had a PR out to fix this once before. :P

@saltman424
Copy link

@weswigham since #43887 was never merged, will this issue be addressed by #54866?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs Investigation This issue needs a team member to investigate its status.
Projects
None yet
Development

No branches or pull requests

4 participants