I'm not sure how I might precisely describe my goal in words. Here is some code to explain what I would like to do:
type validTypes = string | number
type item = Record<string, validTypes>
interface Test extends item {
someint: number;
something: string;
}
let test: {
[P in keyof Test]: (arg: Test[P]) => boolean
} = {
someint: (arg) => arg > 0,
something: (arg) => arg == "hi"
}
I'm trying to make the mapped type [P in keyof Test]: (arg: Test[P]) => Test[P] work, however, it's giving me the following error:
Type '{ someint: (arg: number) => boolean; something: (arg: string) => boolean; }' is not assignable to type '{ [x: string]: (arg: validTypes) => boolean; someint: (arg: number) => boolean; something: (arg: string) => boolean; }'.
Property 'someint' is incompatible with index signature.
Type '(arg: number) => boolean' is not assignable to type '(arg: validTypes) => boolean'.
Types of parameters 'arg' and 'arg' are incompatible.
Type 'validTypes' is not assignable to type 'number'.
Type 'string' is not assignable to type 'number'.
This is quite confusing to me, as [P in keyof Test]: Test[P] (without the function) seems to work. The error seems to appear when I try to use Test[P] as a parameter in the function type.
Unfortunately the type of
test(which you can inspect via IntelliSense) is:And this is not a valid type. Indeed, if you tried to create your own variable of that type explicitly, the compiler would complain:
The problem is that
Item, and thusTest, has an index signature which gets mapped according to the same rule as the individualsomeintandsomethingproperties. The index signature property becomes(arg: string | number) => boolean, meaning that every individual property needs to be a function that accepts an argument of typestring | number. But yoursomeintandsomethingproperties become functions that only acceptnumberandstringrespectively. With--strictFunctionTypesenabled (part of the--strictsuite of compiler options), functions need to be contravariant in their parameter types (see Difference between Variance, Covariance, Contravariance and Bivariance in TypeScript). So if you widen the function parameter type you narrow the function type. But the type mapping you apply is unfortunately covariant, and it produces an invalid type to which no value will be seen as assignable.If you're only planning to access the
someintandsomethingproperties oftestthen you don't really care about or want an index signature at all. Indeed I'm not sure you want an index signature inTest. So one way to proceed is to drop the index signature entirely:Here
Testhas no index signature; it's just{someint: number, something: string}. I've created aSatisfieshelper type which acts kind of like a type-levelsatisfiesoperator. The above compiles, soTestis compatible withItem, but you're not extending it. And so the following compiles:Or, if you need
Testto have the index signature you can build it in two parts:And then make sure
testmaps overTestWithoutIndexSignatureinstead ofTest:Finally, if you can't change
Testat all, then you can computeTestWithoutIndexSignaturefrom it, using key remapping in mapped type to filter outstringfrom the keys:The particular approach is up to you, but the goal here is not to let
testhave an index signature, since it only hurts you.Playground link to code