🔍 搜索词
#57002
#41874
函数交集,返回类型,不完整,声明顺序依赖
✅ 可可行性检查清单
- 这不会对现有的TypeScript/JavaScript代码造成破坏性的变化
- 这不会改变现有JavaScript代码的运行时行为
- 这可以实现而无需根据表达式的类型发出不同的JS
- 这不是一个运行时特性(例如库功能、JavaScript输出的非ECMAScript语法、JS的新语法糖等)
- 这不是一个请求添加新实用类型的请求:https://github.com/microsoft/TypeScript/wiki/No-New-Utility-Types
- 这个特性将与我们设计目标一致:https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals
⭐ 建议
函数交集的返回类型至少应该是完整的,并且独立于函数声明的顺序。
📃 激励示例
- 示例1:非重载函数的交集
interface A0 {
foo(): string;
}
interface B0 {
foo(): number;
}
declare const ab0: A0 & B0;
declare const ba0: A0 & B0;
const rab = ab0.foo(); // actual string, expecting string | number
const rba = ba0.foo(); // actual number, expecting string | number
- 示例2:重载函数的交集
interface A1 {
bar(x:1): 1;
bar(x:2): 2;
}
interface B1 {
bar(x:2): "2";
bar(x:3): "3";
}
declare const ab1: A1 & B1;
const rab11 = ab1.bar(1); // actual 1, expecting 1
const rab12 = ab1.bar(2); // actual 2, expecting 2 | "2"
const rab13 = ab1.bar(3); // actual 3, expecting 3
declare const ba1: B1 & A1;
const rba11 = ba1.bar(1); // actual 1, expecting 1
const rba12 = ba1.bar(2); // actual "2:, expecting 2 | "2"
const rba13 = ba1.bar(3); // actual 3, expecting 3
- 示例3:具有对象返回类型的函数的交集
interface A2 {
bar(): {a: string};
}
interface B2 {
bar(): {b: string};
}
declare const ab2: A2 & B2;
declare const ba2: B2 & A2;
const rab21 = ab2.bar(); // actual: {a: string}, expecting: {a: string} | {b: string}
const rba21 = ba2.bar(); // actual: {b: string}, expecting: {a: string} | {b: string}
当前算法:
- 将
g = g[0] & g[1] & ...
当作有序重载函数{ g[0] ; g[1] ; ....}
来处理。 - 让
args
成为参数。 - return
ReturnType<chooseOverload(g, args)>
当前算法将 g
完全视为有序重载函数 { g[0] ; g[1] ; ....}
。因此,args
可以匹配最多一个交集成员 g[i]
,从而导致不完整且依赖于声明顺序的返回类型。
提议算法 # 1:
- 让
g = g[0] & g[1] & ...
成为函数的交集。 - 让
args
成为参数。 returnType = never
//初始化- 对于
g[i]
在g
中 - 如果
g[i]
是重载函数 returnType = returnType | ReturnType<chooseOverload(g[i], args)>
- 否则如果
args
扩展自Parameters<g[i]>
则 returnType = returnType | ReturnType<g[i]>
- return
returnType
这对于上述示例应该有效。
提议算法 # 2:
与当前算法相比,提议算法 # 1是一个更昂贵的计算,但它是完整的,并且不依赖于声明顺序。如果那个计算太昂贵了,那么可以使用一个更简单的算法:
- 返回类型是
i
对ReturnType<g[i]>
的并集。如果g[i]
是重载函数,则其计算方式为该重载序列的 catch-all(也称为覆盖)情况的返回类型。
理由:
- 让
cover(g)
成为 catch-all(也称为覆盖)情况(即重载序列中的最后一个)。也就是说,对于每个参数索引paramIndex
,Parameters<cover(g)>[paramIndex]
是 x1m26n
9条答案
按热度按时间iecba09b1#
这听起来像是 #57089 的重复问题。为什么要打开另一个问题?你可以直接编辑并打开你的另一个问题。 🤷♂️
const rab = ab0.foo(); // 实际字符串,期望字符串 | 数字
这仍然对我来说没有意义。你有一个总是返回
string
的函数,另一个总是返回一个number
的函数。然后你说你们有它们的交集,这意味着返回类型也必须是交集,而不是联合。所以它应该是string & number
而不是string | number
。nbysray52#
老实说,我在这里看不到价值。提议的算法在显式编写的重载和由交集形成的重载之间引入了不一致性,并破坏了人们肯定编写的实际代码,其中较早的重载返回比它遮蔽的更具体的类型。我们得到了什么回报?这似乎像是用一个问题换取另一个问题。
wwodge7n3#
MartinJohns
然后你说它们有交集,这意味着返回类型也必须是交集,而不是并集。为什么一定要这样呢?如果它是交集,那么
的返回类型将永远不会是交集。这似乎是通过矛盾证明返回类型不是交集。我不明白我们如何为这个特殊情况开一个例外,所以它必须适用于所有情况。
fsi0uk1n4#
@RyanCavanaugh
提议的算法...破坏了人们编写的实际代码,其中较早的重载返回的类型比它所遮蔽的类型更具体。
例如:
说实话,我看不出这里的价值。提议的算法在显式编写的重载和由交集形成的重载之间引入了不一致性,并破坏了人们编写的实际代码,其中较早的重载返回的类型比它所遮蔽的类型更具体。我们得到了什么回报?这似乎像是用一个问题换取另一个问题。
在上面的例子中,提案将返回
"emergency" | number
,它更宽。而当前的TypeScript对于
A&B
和B&A
都返回"emergency"
,但它只适用于B&A
,因为TypeScript已经将B&A
视为一个重载,并重新排序了重载的成员,这对复杂重载来说是有代价的 - 在计算时间和复杂性/维护逻辑方面都有代价。然而,对于独立且无序的重载情况,当前算法选择了一个任意类型,这个类型必须太窄。太窄的类型比太宽的问题要严重得多。
mutmk8jj5#
为什么必须这样?
因为这是唯一合乎逻辑的做法。联合类型没有意义且不安全。
(()=>string) & (()=>number)
可以分配给(()=>string)
,因此调用者会期望一个string
。它也可以分配给(()=>number)
,因此调用者会期望一个number
。在某些情况下返回一个string
,在其他情况下当每个相应类型都说它总是一个number
(即联合类型)时,返回一个string
或number
是错误的。是的,逻辑结果是返回类型最终变为
never
,因为(()=>string) & (()=>number)
是一种无法在 JavaScript 中实现的类型。你不能有一个函数同时返回一个string
和一个number
,但这就是这种类型所说的。pgx2nnw86#
@MartinJohns - 一个
&
函数表示一组重载序列的成员,但不表示它们的顺序,因此包括所有可能的顺序。无论选择哪种重载顺序,但在调用点(如果有多个匹配项,则为一个或多个)将匹配一个(或多个)重载函数,该重载函数的返回类型将作为输出。在这里,参数
f
到g
必须是一个重载函数。f
的返回类型将是string
或number
,具体取决于未知的重载顺序,因此类型必须是string|number
。当然,这是一个非常奇怪的边缘情况,因为两个重载顺序都有不合理的阴影,所以它永远不会被实现为重载函数,而是作为单个函数。如果算法能处理这样的边缘情况,那么在使用这个建议的情况下就会发生这种情况。
最后,但同样重要的是,在原始帖子 示例1 中,当前的行为不是
never
而是string
。它是
string
,因为当参数{()=>string;()=>number}
传递给chooseOverload
时,第一个重载函数被返回。tag5nh1u7#
以下是关于当前行为的至少一个证明:
如果,正如你所Assert的那样,
(A0 & B0)["foo"]
和(B0 & A0)["foo"]
都返回了never
,那么显然test2.foo()
必须返回never
,但它却返回了string|number
。我写这篇文章的目的是主张,对于
(A0 & B0)["foo"]
和(B0 & A0)["foo"]
的每一种情况,都应该返回string|number
,而它们实际上只分别返回了string
和number
,这是由于 TypeScript 目前将&
-intersection 错误地当作一个重载连接操作符。thtygnil8#
老实说,我觉得这里没有什么价值。提议的算法在显式编写的重载和由交集形成的重载之间引入了不一致性,并破坏了人们编写的实际代码,其中较早的重载返回的类型比它遮蔽的类型更具体。我们能得到什么回报呢?这似乎像是用一个问题换来另一个问题。
这些已经不一致(#56951),除非交集以特定顺序排列,否则
ReturnType
将给出错误的结果:用户本意应该做的是:
除非人们遵循这个规则,否则
ReturnType
无法以多种方式完成其工作,包括这个。对于像这样完全没有运行时对应物的东西,类型系统对它没有什么有意义的话可说。我宁愿让交集具有一些与顺序无关的行为,特别是考虑到当前提供的顺序相关行为与函数重载不匹配。
xsuvu9jc9#
我认为这个提案的写法是不可行的。正确返回类型应该是适用签名的返回类型的交集。
也就是说,
type F = ((x:X1)=>Y1) & ((x:X2)=>Y2)
应该被推断为:x
满足X1
和X2
,则为Y1 & Y2
。x
满足X1
但不满足X2
,则为Y1
。x
满足X2
但不满足X1
,则为Y2
。x
满足X1 | X2
,且两者都不是彼此的合适子类型,则为Y1 | Y2
。这意味着您仍然可以指定一个最通用的重载函数,但可以获得更具体重载函数的好处。
示例 1:
ab0
和ba0
都是never
示例 2:
ab1.bar(2)
和ba1.bar(2)
都是never
示例 3:
rab21
和rba21
都是{a:string, b:string}