Skip to content

Covariance breaks when checking for undefinded in a type #55161

Open

Description

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

Activity

  1. RyanCavanaugh commented on Jul 27, 2023

    @RyanCavanaugh
    Member

    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 Anders Hejlsberg (@ahejlsberg)

  2. weswigham commented on Jul 27, 2023

    @weswigham
    Member

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

  3. saltman424 commented on Dec 14, 2023

    @saltman424

    Wesley Wigham (@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

Metadata

Metadata

Labels

Needs InvestigationThis issue needs a team member to investigate its status.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions