控制流分析在使用 TypeScript 的泛型时为什么会失败

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

Why does control flow analysis fail when using generics in typescript

问题

以下是您要求的翻译:

我有一个通用类型,允许对象中的任一属性为 `undefined`,但不允许同时都为 `undefined`

```typescript
type RequireOne<T> =
  | {
      a: NonNullable<T>
      b: NonNullable<T>
    }
  | {
      a: NonNullable<T>
      b: undefined
    }
  | {
      a: undefined
      b: NonNullable<T>
    }

现在,如果我在声明函数时使用这个类型,它与具体类型一起工作得很好:

const test1 = ({ a, b }: RequireOne<string>): string => a ?? b

但如果我尝试将相同的函数也变成泛型,它会失败:

const test2 = <T>({ a, b }: RequireOne<T>): T => a ?? b
// 错误: 类型 'NonNullable<T> | undefined' 无法赋值给类型 'T'。

这是否是 TypeScript 的预期行为,如果是的话,为什么呢?


<details>
<summary>英文:</summary>

I have a generic type that allows for either of the properties in an object to be `undefined`, but never both:

type RequireOne<T> =
| {
a: NonNullable<T>
b: NonNullable<T>
}
| {
a: NonNullable<T>
b: undefined
}
| {
a: undefined
b: NonNullable<T>
}


Now, if I use this type when declaring functions, it works beautifully with concrete types:

const test1 = ({ a, b }: RequireOne<string>): string => a ?? b


But it fails if I try to make the same function generic as well:

const test2 = <T>({ a, b }: RequireOne<T>): T => a ?? b
// ERROR: Type 'NonNullable<T> | undefined' is not assignable to type 'T'.


Is this expected behavior from TypeScript, and if so, why?

</details>


# 答案1
**得分**: 2

一般来说,[泛型](https://www.typescriptlang.org/docs/handbook/2/generics.html)和[控制流分析](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#control-flow-analysis)不太兼容。在这种特殊情况下,看起来您遇到了[microsoft/TypeScript#50652](https://github.com/microsoft/TypeScript/issues/50652)中报告的问题,其中[联合类型](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types)在通用判别属性时不被视为[可判别联合类型](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions)。

在这里,您试图通过检查`RequireOne&lt;T&gt;`的`a`属性是否为[nullish](https://developer.mozilla.org/en-US/docs/Glossary/Nullish)来进行判别,但类型检查器不会这样做。

由于已经有一个关于这个问题的问题报告,因此至少可以说这个行为是“已知的”,但这并不一定意味着它是“预期的”,因为该问题在[Backlog](https://github.com/microsoft/TypeScript/milestone/29)上,并标记为“需要调查”(截至2023年08/04)。所以它可能不会在一段时间内或永远解决。

但总的来说,TypeScript 实际上不能对泛型类型执行任意的高阶分析。[`NonNullable&lt;T&gt;`](https://www.typescriptlang.org/docs/handbook/utility-types.html#nonnullabletype)在[TypeScript 4.8](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-8.html#improved-intersection-reduction-union-compatibility-and-narrowing)中等效于[交叉类型](https://www.typescriptlang.org/docs/handbook/2/objects.html#intersection-types)`T &amp; {}`。对于任何*特定*类型`T`,编译器会理解`T &amp; {}`可以与`undefined`区分开来。但它不能将这种情况抽象为一般的语句:“对于所有类型`T`,`T &amp; {}`可以与`undefined`区分开来”。除非有人将其硬编码到编译器中,否则您将遇到这个问题。

<details>
<summary>英文:</summary>

Generally speaking [generics](https://www.typescriptlang.org/docs/handbook/2/generics.html) and [control flow analysis](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#control-flow-analysis) don&#39;t play nicely together.  In this particular case it looks like you&#39;ve run into the issue reported in [microsoft/TypeScript#50652](https://github.com/microsoft/TypeScript/issues/50652) in which a [union](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types) is not seen as a [discriminated union](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions) when the intended discriminant property is generic.  

Here you are trying to discriminate `RequireOne&lt;T&gt;` by checking the `a` property for [nullishness](https://developer.mozilla.org/en-US/docs/Glossary/Nullish), but the type checker doesn&#39;t do it.  

Since there&#39;s an issue about it then the behavior is at least *known*, but that doesn&#39;t *necessarily* mean it&#39;s &quot;expected&quot; given that the issue is on the [Backlog](https://github.com/microsoft/TypeScript/milestone/29) and marked as &quot;Needs Investigation&quot; (as of today, 2023/08/04).  So it might not get resolved for a while, if ever.

But again, in general, TypeScript really can&#39;t perform arbitrary higher order analysis on generic types.  [`NonNullable&lt;T&gt;`](https://www.typescriptlang.org/docs/handbook/utility-types.html#nonnullabletype) is [as of TypeScript 4.8](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-8.html#improved-intersection-reduction-union-compatibility-and-narrowing) equivalent to the [intersection type](https://www.typescriptlang.org/docs/handbook/2/objects.html#intersection-types) `T &amp; {}`.  For any *specific* type `T`, the compiler will understand that `T &amp; {}` is distinguishable from `undefined`.  But it can&#39;t abstract such things to the general statement &quot;for all types `T`, `T &amp; {}` is distinguishable from `undefined`&quot;.  Until and unless that is hardcoded into the compiler by someone, you&#39;ll have this problem.



</details>



huangapple
  • 本文由 发表于 2023年8月4日 22:32:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/76836884.html
匿名

发表评论

匿名网友

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

确定