涉及泛型对象的泛型属性的赋值无法在泛型函数中正确进行类型检查

Posted

技术标签:

【中文标题】涉及泛型对象的泛型属性的赋值无法在泛型函数中正确进行类型检查【英文标题】:Assignment involving generic property of generic object fails to typecheck correctly within generic function 【发布时间】:2019-09-29 23:30:42 【问题描述】:

我有一个通用函数,可以读取或写入给定对象的调用者选择的属性。我使用类型约束来确保传递的键是用于可分配给相关类型或从相关类型分配的属性。调用代码似乎可以正确地进行类型检查。实现中对象属性的使用未按预期进行类型检查。

在本例中,我使用 boolean 作为预期类型。我已经评论了没有按预期进行类型检查的行。 You can also see this example in the typescript playground here.

如何表达booleanAssignmentTest 的签名,以便类型检查器理解obj[key] 的类型为boolean?是否可以以保持boolean 本身通用的方式完成,以允许统一定义多个与其他类型一起使用的类似函数?

type KeysOfPropertiesWithType<T, U> = 
  // We check extends in both directions to ensure assignment could be in either direction.
  [K in keyof T]: T[K] extends U ? (U extends T[K] ? K : never) : never;
[keyof T];

type PickPropertiesWithType<T, U> = Pick<T, KeysOfPropertiesWithType<T, U>>;

function booleanAssignmentTest<T extends PickPropertiesWithType<T, boolean>, K extends KeysOfPropertiesWithType<T, boolean>>(obj: T, key: K): void 
    let foo: boolean = obj[key]; // Fine!
    let foo2: string = obj[key]; // No error, but there should be!
    obj[key] = true; // Error: "Type 'true' is not assignable to type 'T[K]'."


let foo =  aBool: false, aNumber: 33, anotherBool: false ;
booleanAssignmentTest(foo, "aBool"); // Fine!
booleanAssignmentTest(foo, "anotherBool"); // Fine!
booleanAssignmentTest(foo, "aNumber"); // Error: working as intended!

我正在使用tsc 3.4.5 版以防万一。

更新:

我在类似问题上找到了以下答案:https://***.com/a/52047487/740958

我尝试应用他们的更简单且效果更好的方法,但是obj[key] = true; 语句仍然存在同样的问题。

function booleanAssignmentTest2<T extends Record<K, boolean>, K extends keyof T>(obj: T, key: K): void 
    let foo: boolean = obj[key]; // Fine!
    let foo2: string = obj[key]; // Error: working as intended!
    obj[key] = true; // Error: "Type 'true' is not assignable to type 'T[K]'."


let foo =  aBool: false, aNumber: 33, anotherBool: false ;

booleanAssignmentTest2(foo, "aBool"); // Fine!
booleanAssignmentTest2(foo, "anotherBool"); // Fine!
booleanAssignmentTest2(foo, "aNumber"); // Error: working as intended!

This ^^ example on TS Playground.

【问题讨论】:

也可能相关:***.com/questions/52188399/… 特别是用户@jcalz 提到的地方:“编译器不够聪明,无法意识到 T[K] 在一般情况下可分配给数字”。尽管我的问题似乎相反,但该布尔值不能分配给 T[K]。 我可能对这个级别的 Typescript 有点陌生,但不是说 'boolean' 不是对象类型,所以虽然它有值 'true' 和 'false' ,它实际上并没有代表“真”和“假”的属性,因此您无法真正确定 T[K] 是否扩展了 U 或反之亦然 - 因为它不能。实际上,对于 boolean/string/number,您必须指定其中一种类型的 instanceof。还是我错过了什么? 【参考方案1】:

第一个选项(使用 KeysOfPropertiesWithType)不起作用,因为 typescript 无法推理仍然包含未解析类型参数的条件类型(例如本例中的 TK

第二个选项不起作用,因为T extends Record&lt;K, boolean&gt; 意味着T 可以是例如 a: false ,这意味着分配obj[key] = true 将无效。通常,T[K] 必须扩展一个类型这一事实并不意味着在泛型函数内部我们可以为它分配任何值,约束只是告诉我们该值的最低要求是什么,我们还不知道完整的合同T[K] 要求。

至少对您的示例代码有效的解决方案是根本不使用T。在这种情况下似乎没有必要:

function booleanAssignmentTest2<K extends PropertyKey>(obj: Record<K, boolean>, key: K): void 
    let foo: boolean = obj[key]; // Fine!
    let foo2: string = obj[key]; // Error: working as intended!
    obj[key] = true; // Ok now we know T[K] is boolean


let foo =  aBool: false, aNumber: 33, anotherBool: false ;

booleanAssignmentTest2(foo, "aBool"); // Fine!
booleanAssignmentTest2(foo, "anotherBool"); // Fine!
booleanAssignmentTest2(foo, "aNumber"); // Error: working as intended!

如果你的例子比较复杂,请提供一个完整的例子,虽然如果你确定值可以分配给T[K],通常解决方案将使用类型断言,所以这是一个可能的解决方案:

function booleanAssignmentTest2<T extends Record<K, boolean>, K extends keyof T>(obj: T, key: K): void 
    let foo: boolean = obj[key]; // Fine!
    let foo2: string = obj[key]; // Error: working as intended!
    obj[key] = true as T[K]; // ok now

【讨论】:

以上是关于涉及泛型对象的泛型属性的赋值无法在泛型函数中正确进行类型检查的主要内容,如果未能解决你的问题,请参考以下文章

为啥 TS 中的泛型接口不能正确推断类型?

泛型编程的术语

C# 使用自定义的泛型函数/方法对泛型数组进行四则运算

泛型编程类型约束与软件扩展性--面向可扩展的泛型编程就是面相类型约束编程

对泛型编程中泛型类型的一些理解

Java泛型边界问题,super关键字