TypeScript 索引访问类型约束行为异常

TypeScript indexed access type constraints behaving strangely

举个例子

interface Person<TName> {
    name: TName;
}

type People =
    | Person<"Levi">
    | Person<"Julien">

type FilteredPersonBase<TPerson extends People> = TPerson["name"] extends "Levi" ? TPerson : never;
type FilteredPerson = FilteredPersonBase<People>; // never

type FilteredPersonBase2<TPerson extends People> = TPerson extends { name: "Levi" } ? TPerson : never;
type FilteredPerson2 = FilteredPersonBase2<People>; // Person<"Levi">

我希望 FilteredPersonFilteredPerson2 都解析为 Person<"Levi">,但它们没有。为什么在类型约束中使用索引访问运算符的解析方式与使用内联类型不同?

我同意第二个应该解析为人 Person<"Levi">,但让我们看看这是如何发生的:

FilteredPersonBase2<People>
    // FilteredPersonBase2 is a distributive conditional type since
    // TPerson appears nakedly in the condition of the conditional type
    => FilteredPersonBase2<Person<"Levi">> | FilteredPersonBase2<Person<"Julien">>
    // The conditional type is applied to each member of the union
    => (Person<"Levi"> extends { name: "Levi" } ? Person<"Levi"> : never) |  (Person<"Julien"> extends { name: "Levi" } ? Person<"Julien"> : never)
    // The first application return Person<"Levi">, the second resolves to never
    => Person<"Levi"> | never
    // Never is removed in a union 
    => Person<"Levi">

这样做的原因是 distributive behavior of conditional types

但是条件类型只分布在裸类型参数上。在第一个示例中,条件超过 TPerson["name"],这不是裸类型参数,因此不会发生分配。展开我们得到的条件类型:

FilteredPersonBase<People> =>
    // No distribution 
    People["name"] extends "Levi" ? People : never => 
    // People["name"] just resolves to the union
    "Levi" | "Julien" extends "Levi" ? People : never => 
    // The union does not extend a constituent of the union
    never 

由于没有发生分配,TPerson["name"] 只是解析为 "Levi" | "Julien" 并且联合不会扩展成分。反之亦然 ("Levi" extends TPerson["name"]),但这只会解析为联合 (Person),因为联合的组成部分是联合的 sub-type。

只是为了好玩,您可以使用 TPerson extends TPerson:

等始终为真的条件强制在 TPerson 上分发
type FilteredPersonBase<TPerson extends People> = TPerson extends TPerson ? TPerson["name"] extends "Levi" ? TPerson : never : never;
type FilteredPerson = FilteredPersonBase<People>;  // Person<"Levi">