TypeScript 协方差在检查类型中的未定义时中断,

pcww981p  于 23天前  发布在  TypeScript
关注(0)|答案(3)|浏览(16)

Bug报告

我正在使用 solid-js npm 包,它定义了一个名为 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);

我不太理解其中的逻辑,但它给我带来了一些问题:

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!

我创建了一个具有属性 0 的对象,该属性包含一个 Setter,似乎它不是协变的。
检查错误时,看起来问题起源于这部分 undefined extends T ? () => undefined : {},实际上,如果我删除它,这就可以工作了,而且即使这是类型的唯一部分,问题仍然会发生:

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!

我认为这是一个错误,因为类型是相同的:

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

此外,如果将 Test 定义为 [ Setter<T> ],则不会发生错误:

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?!?!

🔎 搜索词

  • undefined extends T
  • 协变性
  • 协变

🕗 版本与回归信息

此更改发生在版本 3.3.33.5.1 之间。(在所有版本中都不会发生的唯一Playground版本是 3.3.3)

⏯ Playground链接

Playground链接

💻 代码

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>;
//   ^?

🙁 实际行为

无法将 b 的值分配给 a
至少它是错误的,因为它不一致。

🙂 预期行为

可以将 b 的值分配给 a

6g8kf2rb

6g8kf2rb1#

我相信这里的错误是我们没有将 U extends T 标记为在 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

jaxagkaj

jaxagkaj2#

我之前以为我已经有一个PR来修复这个问题了。:P

svmlkihl

svmlkihl3#

由于#43887从未被合并,#54866是否会解决这个问题?

相关问题