The issue arises from the resemblance between index signatures and mapped types. Despite their similarities, they have distinct rules and effects.
An index signature can coexist with other object type properties and signatures, with a syntax like
type IndexSig = { [dummyKeyIdentifier: KeyType]: ValueType }
Here, dummyKeyIdentifier
serves as a placeholder for documentation purposes but is not visible to the type system. There are strict limitations on what KeyType
can be, typically limited to string
, number
, symbol
, "pattern" template literal types, or unions of these types. Using string/number literals like "a"
or 3
, generic types such as K
, or types involving this
are invalid in an index signature context.
Thus, errors may occur in your code.
On the contrary, a mapped type stands alone within curly braces without any additional properties or signatures, following a syntax like
type MappedType = { [K in KeyType]: ValueType<K> }
In this case, K
acts as a generic type parameter iterating over union members of
KeyType</code, influencing the property type. Unlike index signatures, <code>K
can be reused within a mapped type to process different tasks for each key in
KeyType
.
The regulations for KeyType
are more lenient in a mapped type, allowing various property-like types such as strings, numbers, symbols, pattern template literals (converted into equivalent index signatures), literal types, generic types, and unions.
Distinguishing between them, an index signature contains a colon (:
) inside square brackets, while a mapped type includes the in
keyword.
Occasionally, the compiler might detect improper usage of an index signature and recommend switching to a mapped type instead. Even when such detection does not happen, the root cause usually lies in this distinction.
To rectify your scenario, converting the index signature to a mapped type yields functional code:
declare abstract class BaseModel {
abstract fields: Fields<any>;
set<K extends keyof Fields<this>>(field: K, value: Fields<this>[K]): this;
set<K extends keyof Fields<this>>(
fields: { [P in keyof Fields<this>]: Fields<this>[K] } // okay
): this;
}
By making this adjustment, the functionality should improve.
Access the Playground here