What causes functions operating on mapped objects with computed keys to not correctly infer types?

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.

Answer â„–1

I extended the link I shared in the comments a bit more, and it appears that you can effectively solve this by providing hints to the compiler for type checking. If you configure your tsconfig file to highlight any types, your code will be safer:

Playground

type Original = { bar: number }

type MappedC = {
  [prop in keyof Original as `$(${prop})`]: (arg: Original[prop]) => Original[prop]
}

const propSelector =  <propName extends string>(propName: propName) => `$(${propName})` as const

const c1: MappedC = {
  [propSelector('bar')]: (arg) => 5, // correct usage, `arg` not inferred. Return type checked & passed.
}

const c2: MappedC = {
  [propSelector('bar')]: (arg) => "I am a string", // incorrect usage, arg not inferred. Return type checked & failed.
}

// Should be equivalent to c1 but not. 🤔🤔🤔🤔🤔
const c3: MappedC = {
  ["$(bar)"]: (arg) => 5, // correct usage, `arg` inferred. Return type checked & passed.
}

// Let's help the type checker

const c4: MappedC = {
  [propSelector('bar')]: (arg: number) => 5, // Correct usage, all good!
}

const c5: MappedC = {
  [propSelector('bar')]: (arg: string) => 5, // Incorrect usage, an error is flagged!
}

Meanwhile, I suggest raising an issue with the compiler.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Divs sliding out of alignment

I am experiencing an issue with the scrolling behavior of a wrapper div that contains two nested divs. Specifically, when I scroll the wrapper horizontally on Android devices, the header section and content section seem to be out of sync and there is a not ...

Select checkboxes by clicking a button that matches the beginning of a string in JavaScript

I have a form with a list of users and checkboxes below their names. Each user has a button that should select all checkboxes assigned to them. Below is the code for the dynamically created buttons and checkboxes. The included function takes the form name ...

What is the method for generating a data type from an array of strings using TypeScript?

Is there a more efficient way to create a TypeScript type based on an array of strings without duplicating values in an Enum declaration? I am using version 2.6.2 and have a long array of colors that I want to convert into a type. Here is what I envision: ...

Unable to activate click function in Jquery

Here is a basic HTML page snippet: <html> <head> <script src="http://code.jquery.com/jquery-latest.min.js" type="text/javascript"> </script> <script> $(document).ready(function () { $('#test').click(); }); < ...

Is there a method we can use to replace fixture fields with data created during the test case to produce a dynamic payload? (Already attempted existing solution)

I am new to using Cypress and I'm wondering if there is a way to generate a dynamic payload by replacing values in a JSON file with values generated programmatically in a Cypress test. This is similar to what we do in Rest Assured by substituting %s i ...

typescriptIs there a more efficient approach to typing optional values without a default value in

In my React application with TypeScript, I am using the following code to provide typed props: type ScheduleBoxContentProps = { desc: ReactNode, lottie: LottieProps, } & Partial<{className: string}>; I want the className prop to be optional ...

What are the reasons behind the inability to import an image file in Next.js?

Having an issue with importing image file in Next.js I'm not sure why I can't import the image file... The image file is located at 'image/images.jpg' In Chrome browser, there are no error messages related to the image, However, in ...

The current version of HTML5 Context Menus is now available

I'm in need of implementing the HTML5 Context Menu feature. Currently, only Firefox supports it. My main objective is to add some menu options without replacing the existing context menu. How can I achieve the same functionality now? I am aware of va ...

Display or conceal elements by utilizing ng-show/ng-hide according to specific conditions

Here is the code snippet I am working with: <input class="form-field form-control" type="text" name="Website" ng-model="vm.infodata.Website" placeholder="Website Address" maxlength="50" required ng-pattern="/^(www\.)?[a-zA-Z0-9_&bs ...

Elements of Data Pagination in Vuetify Data Tables

My data-table is filled with thousands of data inputs, so I am using the default Vuetify pagination to display only 5, 10, or 25 items at a time on the table. However, I am in need of a way to determine which data is currently visible on the table. For ex ...

Mastering div manipulation with jQuery: A step-by-step guide

I have three divs with the classes "col-md-2," "col-md-8," and "col-md-2." What I want is that when a button in the "col-md-8" div is clicked, both of the other divs should be hidden and the "col-md-8" div should expand to occupy the full width of "col-md ...

What is the specific jQuery event triggered when utilizing the append function on a textarea element?

I am currently setting up a system to detect any modifications in a textarea: <textarea id="log-box__data"></textarea> Modifications are made to the textarea exclusively using jQuery's append method: $(document).on('click', &a ...

Click to shift the div downwards

Currently, I have a piece of javascript applied to a div that directs the user to a specific link: <div style="cursor:pointer;" onclick="location.href='http://www.test.com';"> I am wondering if there is a way to add an effect where, upon ...

1. "Ensuring the URL of a New Tab Using WDIO"2

During my testing scenario: Navigate to link1 Click a button Open a new tab with link2 How should I verify the link2? I attempted using assert(browser).toHaveUrlContaining(''), but it only verified the link1, causing my test to fail. ...

Angular - Dividing Functionality into Multiple Modules

I am currently working with two separate modules that have completely different designs. To integrate these modules, I decided to create a new module called "accounts". However, when I include the line import { AppComponent as Account_AppComponent} from &a ...

Can WebDriverJS be compiled without code minimization using Google Closure Compiler?

I am in need of customizing WebDriverJS to suit my specific requirements. However, I am encountering difficulties with debugging the compiled source code. Having descriptive function names and comments would greatly assist me! Therefore, I am curious to kn ...

Determine the absolute path with respect to a different reference path, rather than the current working directory

One of the challenges I'm facing with my Node.js module is how to easily allow developers to edit the location of the dependencies relative to the module file itself. The issue arises because the current working directory, accessed through `process.cw ...

Removing double double quotes for Javascript

My problem involves a string that represents longitude/latitude in the format of dd°mm'ss''W (note 2 single quotes after ss). To convert this string into its decimal representation, I am using the following code snippet: function dmsTodeg ...

What is the best way to render CSS files in express.js?

My file organization looks like this: node_modules structures {HTML Files} styles {CSS Files} app.js package-lock.json package.json I have already imported the following: const express = require('express'); const app = express(); const p ...

Navigating with Nokia Here maps: plotting a path using GPS coordinates

I am currently developing a GPS tracking system and my goal is to visually represent the device's locations as a route. The challenge I'm facing is that there is a REST API available for this purpose, but my client-side application relies on soc ...