英文:
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)?
错误是在告诉我,我可以想出一个T
和K
,其中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<T extends object, K extends keyof T>(a: Pick<T, K>, b: Omit<T, K>): T {
return { ...a, ...b } // TS2322: Type 'Pick<T, K> & Omit<T, K>' is not assignable to type 'T'. 'Pick<T, K> & Omit<T, K>' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'object'.
}
I got the error above, while the code below obviously works:
interface Test {
a: string,
b: string
}
const a: Pick<Test, 'a'> = { a: 'a' }
const b: Omit<Test, 'a'> = { b: 'b' }
const t: Test = { ...a, ...b }
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)?
Edit 1:
What's even stranger is that TS itself considers T extends Pick<T, K> & Omit<T, K>
true:
function foo<T extends object, K extends keyof T>(a: Pick<T, K>, b: Omit<T, K>): T extends Pick<T, K> & Omit<T, K> ? T : any {
const res: Pick<T, K> & Omit<T, K> = { ...a, ...b }
return res // TS2322: Type 'Pick<T, K> & Omit<T, K>' is not assignable to type 'T extends Pick<T, K> & Omit<T, K> ? T : any'.
}
Edit 2:
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 exists in Pick<T, K> & Omit<T, K>
:
function foo<T, K extends keyof T>(x: Pick<T, K> & Omit<T, K>): T {
return x // TS2322: Type 'Pick<T, K> & Omit<T, K>' is not assignable to type 'T'. 'T' could be instantiated with an arbitrary type which could be unrelated to 'Pick<T, K> & Omit<T, K>'.
}
function bar<T, K extends keyof T>(x: T): Pick<T, K> & Omit<T, K> {
return x // no issue
}
答案1
得分: 3
这里确实存在不完整的可能性。Pick
和 Omit
都被实现为映射类型,它们的结果可能不包含源类型的所有特征。
考虑以下示例:
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>
这样的操作结果时,当 T
和 K
是泛型类型时。类型 T
和 K
更像是占位符,它们的具体类型在函数调用时是未知的,因此编译器只能有限地理解将 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<Test, 'a'> = { a: 'a' }
const b: Omit<Test, 'a'> = { b: 'b' }
const t: Test = { ...a, ...b }
// ~ Type '{ b: string; a: string; }' provides no match
// for the signature '(arg: string): string'
I have given Test
a call signature. Both Pick<Test, 'a'>
and Omit<Test, 'a'>
do not result in a type with a call signature, leading to a situation where Pick<Test, 'a'> & Omit<Test, 'a'>
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<T, K>
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<T, K>
with Omit<T, K>
would mean. You can see a discussion about a similar problem here at #28884.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论