TypeScript 未检测到嵌套数组类型的非法赋值

46scxncf  于 2个月前  发布在  TypeScript
关注(0)|答案(3)|浏览(26)

Typescript不允许将Source4赋值给Target4,因为它们是不兼容的。在3个或更多层级的嵌套数组属性之后,这个错误不再被检测到。即使嵌套类型的错误也可能根据赋值的顺序消失。
注意:每个类型都明确命名,没有循环。(这实际上在我们的生产代码库中造成了问题)

搜索词:嵌套数组赋值、未检测到非法嵌套数组类型赋值、未检测到非法赋值、非法赋值

版本与回归信息:

Playground链接,包含相关代码

实际行为:

  • Source1 可以分配给 Target1
  • Source2 可以分配给 Target2
  • Source3 可以分配给 Target3
  • 重新排序赋值会影响类型检查
  • 如果跳过或在 source1 赋值之后移动 source2 赋值,预期的错误会出现

期望的行为:

  • Source1 ***不***可以分配给 Target1
Type 'Source1' is not assignable to type 'Target1'.
  Types of property 'array' are incompatible.
    Type 'Source2[]' is not assignable to type 'Target2[]'.
      Type 'Source2' is not assignable to type 'Target2'.
        Types of property 'array' are incompatible.
          Type 'Source3[]' is not assignable to type 'Target3[]'.
            Type 'Source3' is not assignable to type 'Target3'.
              Types of property 'array' are incompatible.
                Type 'Source4[]' is not assignable to type 'Target4[]'.
                  Property 'someNewProperty' is missing in type 'Source4' but required in type 'Target4'.
  • 由于 Source1 是一个固定/静态树(没有任何自引用),我希望编译器能找到这个错误。
  • 重新排序赋值不影响类型检查结果
lztngnrs

lztngnrs1#

这里的根本原因是,在嵌套类型评估的某个时刻,我们需要退出,因为我们没有办法知道我们没有遇到一个无限生成递归类型的示例化,例如:

type Nested<T> = {
  a: T;
  b?: Nested<Nested<T>>;
}

“啊,但这个类型只是一次又一次地无限深入地示例化相同的东西,这很容易检测到,而OP完全不同。”你说。但是这里的堆栈看起来和原始报告中的堆栈一样,因为我们一直在访问相同类型的更深层次的示例化——Array<T>。同样的非数组泛型类型也可以观察到这种行为:

type Box<T> = { value: T };

type Source1 = { array: Box<Source2> };
type Source2 = { array: Box<Source3> };
type Source3 = { array: Box<Source4> };
type Source4 = {};

// same as Source types but with "someNewProperty"
type Target1 = {
  array: Box<Target2>;
  // someNewProperty: string // error in target1 assignment as expected if enabled
};
type Target2 = {
  array: Box<Target3>;
  // someNewProperty: string // error in target1 assignment as expected if enabled
};
type Target3 = {
  array: Box<Target4>;
  // someNewProperty: string // error in target1 assignment as expected if enabled
};
type Target4 = {
  someNewProperty: string; // not existing in Source4 => no error in target1 assignment
};

declare const source1: Source1;
declare const source2: Source2;
declare const source3: Source3;
declare const source4: Source4;

// this should not compile:
const target1: Target1 = source1; // comment this line to get errors in target2, target3 assignments or move this line after target2 assignment

// this should not compile:
const target2: Target2 = source2; // error if target1 assignment is commented or after this assignment

所以如果有人想尝试修复这个问题,欢迎提出一些新的检测机制,但是这里的任何修复都需要a)在正常情况下不降低性能,并且b)在实际代码中不引入新的循环或复杂性错误。
请注意,仅仅通过调用堆栈中“相邻”的示例化来检测Nested也是不够的——Nested可能会与另一个类型(甚至通过条件类型与一系列其他类型)进行乒乓球比赛,随着它朝着无穷大前进。

2ul0zpep

2ul0zpep2#

你好,@RyanCavanaugh,
感谢你的快速回复!我认为“Cursed?”是这个问题的正确标签。令我惊讶的是,这个问题尚未被报告。
在使用基于Typescript的ORM(如@prisma)时,在连接某些表时,可能会很快遇到这种问题。在我们的例子中,以下数据结构触发了错误(简化):
Order->Payment[]->Refund[]->RefundOrderItemAmount[]。
我不熟悉Typescript代码库,但根据你关于无限递归 Nested 类型的示例,我确实会问为什么很难检测到。当为 Source1 构建一个“类型树”(我不知道正确的术语)时,与 Nested 类型相比,没有循环或自引用(因此没有通过树返回到 Source1 类型的路径)Ping
天真地,这里不是很容易发现循环吗?
要解析的类型 Ping.pong : Pong.ping => Ping !!️检测到循环。
如果你解析条件类型,是否有可能解析所有可能的方式来检测树和图之间的区别?
但正如我所说,我对Typescript代码库一无所知,也许可以很容易地找到一个更复杂的示例,不容易检测到。
但是无论如何,我觉得你的Source-Box和我的Source-Array示例都很可怕。Typescript承诺是“可信赖的结果”,而这是一个非常简单且不太离谱的例子,其中typescript打破了这个承诺。
如果你能在代码库中给我一些调查此问题的建议,我会很高兴帮助找到解决方案。

tf7tbtn2

tf7tbtn23#

天真地,在这里发现循环难道不是一件轻而易举的事吗?
输入:Ping: Ping.pong=>Pong.ping=>Ping !!️ cycle detected.
这并不是提供的例子中发生的情况。你谈论的是检测循环,但我们在这里讨论的不是循环,而是新类型自始至终的无限下降。换句话说,尽管存在引用循环,但没有相同类型的循环 - 我们讨论的是相同类型的越来越深的示例化。
如果你能在代码库中给我一些起点来调查这个问题,我会很高兴帮助找到一个解决方案。
你可以添加这一行:main...RyanCavanaugh:TypeScript:stackDemo#diff-d9ab6589e714c71e657f601cf30ff51dfc607fc98419bf72e04f6b0fa92cc4b8R22654 以移除深度限制。然后开始尝试找出如何让同一提交中的所有新失败测试再次通过,例如这个:main...RyanCavanaugh:TypeScript:stackDemo#diff-b92eac93adfa5c33adb53f3e9689088003bc273d7ec04382d0573f6944055897R25
这个测试用例(如果没有深度限制,它会失败)是一个很好的演示。为了查看 B<T> 是否是合法的 A<T> ,你需要能够回答 A<B<A<B<A<B<A<B<A<B<T .... 是否与 B<A<B<A<B<A<B<A<B<A<T ... 相关的问题,在无限深度。但是你不能无限深入,也不能简单地说“不”(这里的“不”是错误的答案),所以你必须有一些策略,在有限的时间内终止但仍然产生最可能正确的答案。
警告:如果我们知道如何解决这个问题,它就不会被诅咒(并且已经被修复了)。当然欢迎PR,但这不是我们之前没有尝试过修复它的情况。

相关问题