TypeScript 通用应该在联合中扩展,

nimxete2  于 5个月前  发布在  TypeScript
关注(0)|答案(4)|浏览(70)

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.

🙁 实际行为

tmap(foo, t => [...t]); 中是一个 Maybe<string[]>
似乎当 TMaybe 中是另一个 Maybe 时,它是不透明的(不展开)。

🙂 预期行为

tmap(foo, t => [...t]); 中是一个 string[]
似乎当 TMaybe 中是另一个 Maybe 时,它是展开的。例如,Maybe<Maybe<string[]> > 是展开为 null | (null | string []),然后折叠为 null | string[],这与 Maybe<string[]> 相同。
似乎这是期望的行为,因为在运行时,t 确实只会是 string[]

rvpgvaaj

rvpgvaaj1#

如果你查看foo的quickinfo,原因变得显而易见 - 因为我们现在保留联合起源,null | string[]foo有一个起源Maybe<Maybe<string[]>>。因此,我们将这个与Maybe<T>进行匹配,从而将其解包为Maybe<string[]>。在我们保留那个起源之前,我们会记录foo只是Maybe<string[]>(因为第二个Maybe Package 是一个空操作,结果是相同的类型),并与Maybe<T>进行匹配,得到string[]
@ahejlsberg这似乎又是一个例子,我们的新联合别名恰好产生了更差的推断结果。我知道你已经对此进行了一些调查 - 你对此有什么看法吗?

eyh26e7m

eyh26e7m2#

这是一个棘手的情况。问题在于,当类型是“有损”时,即两个或多个输入类型产生相同的输出类型(例如 Maybe<T>string[]string[] | null 作为输入类型时),那么推理结果就取决于我们是否保留了类型别名。在大多数情况下,保留别名更好,但在这里,我们有明确依赖于有损行为的类型。我的第一个建议是将 Maybe<T> 的声明更改为

type Maybe<T> = [T | null][0];

这将导致我们不保留别名,类似于之前发生的情况。另一种选择是将 map 的声明更改为

const map = <T, U>(
    maybe: T | null,
    mapFn: (value: T) => U,
): Maybe<U> => isResolved(maybe) ? mapFn(maybe) : maybe;

换句话说,通过手动写出 maybe 参数的类型来强制推理成为结构性

1tuwyuhd

1tuwyuhd3#

我将毫不犹豫地承认,这两个建议都是微妙的,但我不确定我能想出更好的方法。

vlf7wbxs

vlf7wbxs4#

感谢@ahejlsberg。我们采纳了你的第一条建议。

我认为这是一个不错的修复方案。我同意它很微妙,而且很难了解TypeScript的(变化)启发式方法,以便选择一种行为或另一种行为。这可能是一个很好的文档机会。

相关问题