具有泛型对象交集的Typescript泛型函数缺少类型错误

qij5mzcb  于 2023-02-13  发布在  TypeScript
关注(0)|答案(2)|浏览(214)

在下面的示例中,Typescript未能标记类型错误:

type Base = {
  endAt?: Date;
};

const someFunction = <T extends Base>(obj: T): (Omit<T, 'endAt'> & { endAt: string }) => {
  return { ...obj, endAt: null };
};

我希望在返回后出现一个错误,说明不能将null赋给string。

ukqbszuj

ukqbszuj1#

这是microsoft/TypeScript#42690中报告的已知错误。当您spreadgeneric类型的对象时,编译器将其近似为泛型类型与对象其余部分类型的交集:

function foo<T extends object>(t: T) {
    const bar = { ...t, a: 1, b: "" };
    /* const bar: T & { a: number; b: string; } */
    return bar;
}

此近似值通常就足够了,但在覆盖属性时可能会出现不希望出现的情况:

const bar = foo({ a: "oops" });
/* const bar: { a: string; } & { a: number; b: string; } */

bar的推断类型显然有一个a属性,它的类型是 * both * string * and * number,这是不可能的,所以bar.a是类型string & number,它被简化为不可能的never类型,并且never类型可以赋值给每一个类型(但反之亦然):

function acceptBooleansOnly(x: boolean){}
acceptBooleansOnly(bar.a); // okay?!

哎呀。
这就是发生在你身上的事情,编译器看到返回类型为T & {endAt: null},它被简化为never(因为null是一个 * 判别属性 *,当判别被认为是不可能的时候,交集会被急切地简化),然后never可以赋值给任何类型,包括Omit<T, 'endAt'> & { endAt: string }

const someFunction = <T extends Base>(obj: T): (
  Omit<T, 'endAt'> & { endAt: string }
) => {
    type Z = T & { endAt: null }
    // type Z = never
    const z = { ...obj, endAt: null };
    // const z: never
    return { ...obj, endAt: null };
};

因此,一个合理但不完美的启发式规则与其他几个合理的类型系统规则的交互导致了这种奇怪的错误行为。
无论如何,microsoft/TypeScript#42690在The Backlog上,这意味着它不会在任何即将发布的版本中被修复。你可能想去那里给它一个机会,并描述为什么你的用例是引人注目的,但实用地说,它可能不会改变太多,你只需要意识到它,并在你的代码中解决它,可能通过重构。👍尽管确切地如何超出了所问问题的范围。 although exactly how is out of scope for the question as asked.
Playground代码链接

omqzjyyz

omqzjyyz2#

这是因为null是TypeScript中字符串类型的有效值。默认情况下,TypeScript将null和undefined视为所有其他类型的子类型,因此可以将它们分配给任何其他类型而不会导致类型错误。可以使用类型保护来确保仅为endAt分配字符串值,如果需要确保不为endAt分配null,则可以使用endAt使endAt成为必需的:字符串而不是endAt?:下面是一个更新后的示例,其中包含一个类型保护:

type Base = {
  endAt?: Date;
};

const someFunction = <T extends Base>(obj: T): (Omit<T, 'endAt'> & { endAt: string }) => {
  const endAtValue = obj.endAt;
  if (endAtValue !== null && endAtValue !== undefined && endAtValue instanceof Date) {
    return { ...obj, endAt: endAtValue.toString() };
  }
  return { ...obj, endAt: null };
};

这样,如果endAt不是Date或null/undefined,代码将不会编译。

相关问题