‘Pick & Omit‘ 无法分配给类型 ‘T’ 是什么原因?

huangapple go评论57阅读模式
英文:

Why Type 'Pick<T, K> & Omit<T, K>' is not assignable to type 'T'?

问题

以下是翻译好的部分:

I found a puzzling error, let's say I want to have a rather useless function which merges two complementing partials of an object:
我发现一个令人困惑的错误,假设我想要创建一个相当无用的函数,该函数合并对象的两个互补部分:

I got the error above, while the code below obviously works:
我得到了上面的错误,而下面的代码显然是有效的:

The error is trying to tell me that I can come up with a T and K where Pick<T, K> & Omit<T, K> is not T. Can somebody give me such an example (or any other explanation for the error above)?
错误是在告诉我,我可以想出一个TK,其中Pick<T, K> & Omit<T, K> 不等于T。有人可以给我一个这样的示例吗(或者对上面的错误进行其他解释)?

What's even stranger is that TS itself considers T extends Pick<T, K> & Omit<T, K> true:
更奇怪的是,TS本身认为T extends Pick<T, K> & Omit<T, K> 为真:

Apparently Pick<T, K> & Omit<T, K> is a subset of T, so there must be a case possible where something exists in T which does not exist in Pick<T, K> & Omit<T, K>:
显然,Pick<T, K> & Omit<T, K>T的子集,因此必须存在一种情况,其中T中存在某些内容,而Pick<T, K> & Omit<T, K> 中不存在:

英文:

I found a puzzling error, let's say I want to have a rather useless function which merges two complementing partials of an object:

function foo&lt;T extends object, K extends keyof T&gt;(a: Pick&lt;T, K&gt;, b: Omit&lt;T, K&gt;): T {
    return { ...a, ...b } // TS2322: Type &#39;Pick&lt;T, K&gt; &amp; Omit&lt;T, K&gt;&#39; is not assignable to type &#39;T&#39;. &#39;Pick&lt;T, K&gt; &amp; Omit&lt;T, K&gt;&#39; is assignable to the constraint of type &#39;T&#39;, but &#39;T&#39; could be instantiated with a different subtype of constraint &#39;object&#39;.
}

I got the error above, while the code below obviously works:

interface Test {
    a: string,
    b: string
}

const a: Pick&lt;Test, &#39;a&#39;&gt; = { a: &#39;a&#39; }
const b: Omit&lt;Test, &#39;a&#39;&gt; = { b: &#39;b&#39; }

const t: Test = { ...a, ...b }

The error is trying to tell me that I can come up with a T and K where Pick&lt;T, K&gt; &amp; Omit&lt;T, K&gt; is not T. Can somebody give me such an example (or any other explanation for the error above)?

Edit 1:

What's even stranger is that TS itself considers T extends Pick&lt;T, K&gt; &amp; Omit&lt;T, K&gt; true:

function foo&lt;T extends object, K extends keyof T&gt;(a: Pick&lt;T, K&gt;, b: Omit&lt;T, K&gt;): T extends Pick&lt;T, K&gt; &amp; Omit&lt;T, K&gt; ? T : any {
    const res: Pick&lt;T, K&gt; &amp; Omit&lt;T, K&gt; = { ...a, ...b }
    return res // TS2322: Type &#39;Pick&lt;T, K&gt; &amp; Omit&lt;T, K&gt;&#39; is not assignable to type &#39;T extends Pick&lt;T, K&gt; &amp; Omit&lt;T, K&gt; ? T : any&#39;.
}

Edit 2:

Apparently Pick&lt;T, K&gt; &amp; Omit&lt;T, K&gt; is a subset of T, so there must be a case possible where something exists in T which does not exists in Pick&lt;T, K&gt; &amp; Omit&lt;T, K&gt;:

function foo&lt;T, K extends keyof T&gt;(x: Pick&lt;T, K&gt; &amp; Omit&lt;T, K&gt;): T {
    return x // TS2322: Type &#39;Pick&lt;T, K&gt; &amp; Omit&lt;T, K&gt;&#39; is not assignable to type &#39;T&#39;. &#39;T&#39; could be instantiated with an arbitrary type which could be unrelated to &#39;Pick&lt;T, K&gt; &amp; Omit&lt;T, K&gt;&#39;.
}

function bar&lt;T, K extends keyof T&gt;(x: T): Pick&lt;T, K&gt; &amp; Omit&lt;T, K&gt; {
    return x // no issue
}

答案1

得分: 3

这里确实存在不完整的可能性。PickOmit 都被实现为映射类型,它们的结果可能不包含源类型的所有特征。

考虑以下示例:

interface Test {
    (arg: string): string
    a: string,
    b: string
}

const a: Pick<Test, 'a'> = { a: 'a' }
const b: Omit<Test, 'a'> = { b: 'b' }

const t: Test = { ...a, ...b }
//    ~ 类型 '{ b: string; a: string; }' 不符合
//      签名 '(arg: string): string' 的要求

我给Test添加了一个调用签名。Pick<Test, 'a'>Omit<Test, 'a'> 都不会产生具有调用签名的类型,导致Pick<Test, 'a'> & Omit<Test, 'a'>不能赋值给Test

还存在另一个问题:编译器缺乏正确分析像 Pick<T, K> 这样的操作结果时,当 TK 是泛型类型时。类型 TK 更像是占位符,它们的具体类型在函数调用时是未知的,因此编译器只能有限地理解将 Pick<T, K>Omit<T, K> 相交会产生什么含义。你可以在#28884中看到关于类似问题的讨论。

示例链接

英文:

There is indeed a possibility for unsoundness here. Both Pick and Omit are implemented as mapped types and their result may not contain all the characteristic of the source type.

Consider this:

interface Test {
    (arg: string): string
    a: string,
    b: string
}

const a: Pick&lt;Test, &#39;a&#39;&gt; = { a: &#39;a&#39; }
const b: Omit&lt;Test, &#39;a&#39;&gt; = { b: &#39;b&#39; }

const t: Test = { ...a, ...b }
//    ~ Type &#39;{ b: string; a: string; }&#39; provides no match 
//      for the signature &#39;(arg: string): string&#39;

I have given Test a call signature. Both Pick&lt;Test, &#39;a&#39;&gt; and Omit&lt;Test, &#39;a&#39;&gt; do not result in a type with a call signature, leading to a situation where Pick&lt;Test, &#39;a&#39;&gt; &amp; Omit&lt;Test, &#39;a&#39;&gt; is not assignable to Test.

There is also another problem: The compiler is lacking the ability to properly analyze the result of operations like Pick&lt;T, K&gt; when T and K are generic types. The types T and K act more like placeholders. Their concrete type is not known until the function is called, so the compiler only has a limited understanding what intersecting Pick&lt;T, K&gt; with Omit&lt;T, K&gt; would mean. You can see a discussion about a similar problem here at #28884.


Playground

huangapple
  • 本文由 发表于 2023年2月16日 09:59:10
  • 转载请务必保留本文链接:https://go.coder-hub.com/75467130.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定