If you are seeking a way to convert the keys of one object, represented as string literals, into slightly modified keys for another expected object in Typescript using template string literals, then I can help. In my version 4.9.5 implementation, I also map the value type from the first object to an argument of a function in the second object, and use it as the return type for added clarity. However, encountering inline expression for a computed key resulted in a peculiar failure of type inference. Below is the code snippet for reproduction:
type Original = { foo: 'expects a string literal', baz: boolean, bar: number }
type Mapped = {
[prop in keyof Original as `$(${prop & string})`]: (arg: Original[prop]) => Original[prop]
}
type PropSelector<name extends string> = `$(${name & string})`
const propSelector = <propName extends string>(propName: propName): PropSelector<propName> => `$(${propName})`
const barKey = propSelector('bar');
const workingTestObject: Mapped = {
'$(foo)': (arg) => 'expects a string literal',
'$(baz)': (arg) => true,
// works just fine as a const (and not inlined)
[barKey]: (arg) => 51345
}
const correctFailures: Mapped = {
// Errors correctly for incorrect return types as well
// Type 'number' not assignable to 'expects a string literal'
'$(foo)': (arg) => 5552,
// Type 'string' not assignable to 'boolean'
'$(baz)': (arg) => '1234',
// Type 'boolean' not assignable to type 'number'
[barKey]: (arg) => true
}
Above, observe that the constant barKey
is the outcome of calling the propSelector
function, yielding '$(bar)'
, which is also the literal return type. The utilization of this constant as a key within the object functions perfectly.
However, simply moving the inline call of the propSelector
function causes the system to break down, leading TypeScript to default the argument to any
, despite maintaining its ability to infer the return type smoothly.
const failingTestObject: Mapped = {
'$(foo)': (arg) => 'expects a string literal',
'$(baz)': (arg) => true,
// type check error: `arg` is implicitly `any`? But return type is fine?
[propSelector('bar')]: (arg) => 13451
}
// return type errors correctly when using propSelector
// but cannot infer the type for arg, so gives "implicit any" error for all of them
const correctFailures: Mapped = {
// return type expected string literal, but got number
[propSelector('foo')]: (arg) => 5552,
// return type expected boolean, but got string
[propSelector('baz')]: (arg) => '1234',
// return type expected number, but got boolean
[propSelector('bar')]: (arg) => true
}
Here's a link to view the above code in a playground environment.
I am meticulous about specifying the literal type returned by the propSelector
function, which creates the key. Therefore, the correct inference occurs when the object key is a string or refers to an expression as a const (such as with
barKey</code), but not when the same expression is simply inlined.</p>
<p>I have experimented with different combinations of the <code>& string
segment in the literal types, including one, the other, both, or neither, yet observed no variance in behavior.
Do you happen to know why this happens? Is it anticipated behavior? Although I could specify the argument type for the function inline, I aim to automate as much type acquisition as possible to avoid redundant typing, making it imperative for me to resolve this issue. Using an inline expression is essential in my case, so resorting to a const is not preferable.
Thank you in advance!
Edit: Surprisingly, a PR was already initiated to rectify this problem. Shortly after posting this query, the PR was merged! You can find the details of the PR here.