Присвоение, включающее универсальное свойство универсального объекта, не может правильно проверить тип в рамках универсальной функции - PullRequest
7 голосов
/ 13 мая 2019

У меня есть универсальная функция, которая читает или записывает свойство выбранного объекта данного объекта.Я использую ограничения типа, чтобы гарантировать, что переданный ключ предназначен для свойства, которое присваивается соответствующему типу или от него.Код вызова, по-видимому, проверяется правильно.Использование свойства объекта в реализации не выполняет проверку типов, как ожидалось.

В этом примере я использую логический тип в качестве ожидаемого типа.Я прокомментировал строки, которые не проверяют тип, как ожидалось. Вы также можете увидеть этот пример на игровой площадке здесь.

Как я могу выразить подпись 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://stackoverflow.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!

Этот пример ^^ на TS Playground.

1 Ответ

4 голосов
/ 03 июня 2019

Первый параметр (с использованием KeysOfPropertiesWithType) не работает, потому что машинопись не может определить условные типы, которые все еще содержат параметры неразрешенного типа (такие как T и K в этом примере)

Второй параметр не работает, поскольку T extends Record<K, boolean> означает, что 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
}
...