Bug报告
🔎 搜索词
通用扩展,联合扩展
🕗 版本与回归信息
- 在版本4.1.5和4.2.3之间发生了变化(新的行为仍然保留在4.3.0-dev.20210407中)
⏯ Playground链接
- Playground链接,其中包含回归的示例
- Playground链接,其中包含导致其他问题的解决方法,但仍可能相关。
💻 代码
type Maybe<T> =
| null
| T
const isResolved = <T>(maybe: Maybe<T>): maybe is T =>
maybe !== null;
const map = <T, U>(
maybe: Maybe<T>,
mapFn: (value: T) => U,
): Maybe<U> => isResolved(maybe) ? mapFn(maybe) : maybe;
// Using an array in this example because it makes the failure obvious when spreading it,
// but the problematic behavior is not specific to arrays.
declare const foo: Maybe<Maybe<string[]>>;
map(foo, t => [...t]); // t is string[] in 4.1, Maybe<string[]> (and error 2488) in 4.2.
map(foo, (t: string[]) => t); // t is string[] in both 4.1 and 4.2 with the annotation.
🙁 实际行为
t
在 map(foo, t => [...t]);
中是一个 Maybe<string[]>
。
似乎当 T
在 Maybe
中是另一个 Maybe
时,它是不透明的(不展开)。
🙂 预期行为
t
在 map(foo, t => [...t]);
中是一个 string[]
。
似乎当 T
在 Maybe
中是另一个 Maybe
时,它是展开的。例如,Maybe<Maybe<string[]>
> 是展开为 null | (null | string [])
,然后折叠为 null | string[]
,这与 Maybe<string[]>
相同。
似乎这是期望的行为,因为在运行时,t
确实只会是 string[]
。
4条答案
按热度按时间rvpgvaaj1#
如果你查看
foo
的quickinfo,原因变得显而易见 - 因为我们现在保留联合起源,null | string[]
在foo
有一个起源Maybe<Maybe<string[]>>
。因此,我们将这个与Maybe<T>
进行匹配,从而将其解包为Maybe<string[]>
。在我们保留那个起源之前,我们会记录foo
只是Maybe<string[]>
(因为第二个Maybe
Package 是一个空操作,结果是相同的类型),并与Maybe<T>
进行匹配,得到string[]
。@ahejlsberg这似乎又是一个例子,我们的新联合别名恰好产生了更差的推断结果。我知道你已经对此进行了一些调查 - 你对此有什么看法吗?
eyh26e7m2#
这是一个棘手的情况。问题在于,当类型是“有损”时,即两个或多个输入类型产生相同的输出类型(例如
Maybe<T>
与string[]
或string[] | null
作为输入类型时),那么推理结果就取决于我们是否保留了类型别名。在大多数情况下,保留别名更好,但在这里,我们有明确依赖于有损行为的类型。我的第一个建议是将Maybe<T>
的声明更改为这将导致我们不保留别名,类似于之前发生的情况。另一种选择是将
map
的声明更改为换句话说,通过手动写出
maybe
参数的类型来强制推理成为结构性。1tuwyuhd3#
我将毫不犹豫地承认,这两个建议都是微妙的,但我不确定我能想出更好的方法。
vlf7wbxs4#
感谢@ahejlsberg。我们采纳了你的第一条建议。
我认为这是一个不错的修复方案。我同意它很微妙,而且很难了解TypeScript的(变化)启发式方法,以便选择一种行为或另一种行为。这可能是一个很好的文档机会。