Incorrect deduction of the argument type for a higher-order function

If I wanted to create a function that takes an object of type T and another value, where the type P should somehow be restricted by T (for example, P should be an array of keys of T), I could easily write it like this:

function customFunction<T, P extends keyof T>(obj: T, p: P[]) {
  // use p to index obj somehow
  return obj;
}

customFunction({ a: 1, b: 'foo' }, ['a']); // Ok
customFunction({ a: 1, b: 'foo' }, ['a', 'b']); // Ok
customFunction({ a: 1, b: 'foo' }, ['a', 'b', 'c']); // Error: 'c' is not a valid key

Now, let's say I want to use this function as an argument for a higher-order method. This method should accept the function alongside a second parameter called arg, and then just call it with this and arg:

class Indexed {
  constructor(public a: number = 1) {}
  public apply<P>(f: (obj: this, arg: P) => this, arg: P) {
    return f(this, arg);
  }
}

const result1 = new Indexed().apply(customFunction, ['a']); // Error: Type 'string' is not assignable to type '"a" | "apply"'
const result2 = new Indexed().apply(customFunction, ['wtf']); // The same error occurs here

However, when using customFunction directly, everything works as expected:

customFunction(new Indexed(), ['a']); // Ok
customFunction(new Indexed(), ['wtf']); // Error, as expected

Playground

The main question is: how can we write the apply method so that it will accept or reject arguments in the same way the customFunction does?

Note that since we don't know the restrictions of customFunction beforehand, we cannot restrict P with the same bounds as in customFunction.

Answer №1

It seems like in this scenario, TypeScript is widening ["foo","bar"] to string[] because it doesn't recognize the need for the type to remain a tuple of string literals such as ["foo", "bar"] (or at least an array of string literals like Array<"foo"|"bar">). In the bar() function, having P constrained to keyof implies to the compiler not to widen string literals to strings. However, there's no similar hint for P in Indexed.app().

You either need to find a way to modify the signature of Indexed.app() to specify that P should be inferred narrowly whenever possible without actually constraining it, or you need to indicate that P should be narrow when calling Indexed.app().


At present, modifying the signature of app() for this purpose involves some unconventional techniques, and until any potential changes are made, it will look something like this:

// The code block example goes here

Opting for hints at the call site can be a simpler approach if the caller remembers to do so:

// The code block example goes here

Alternatively, you could directly specify the type parameter yourself manually:

// The code block example goes here

Hopefully, one of these solutions will be beneficial to your situation. Best of luck!

Link to code

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

Sequentially arranged are the assignments in Firebase functions

I recently came across an article on Firebase task functions published by Google, where it was mentioned that tasks should be dispatched in a first-in-first-out (FIFO) order. Despite trying different settings, the tasks are not being processed in the corre ...

Deleting the last item from an array in Typescript

Consider the following array : ["one-", "two-", "three-", "testing-"] Once converted into a string, it looks like this: "one-,two-,three-,testing-" I need to remove the last character (hyphen) after 'testing' and create a new array from it. ...

How can I set up multiple queues in NestJs Bull?

My goal is to set up multiple queues in NestJs, and according to the documentation: You can create multiple queues by providing multiple comma-separated configuration objects to the registerQueue() method. However, I am encountering an issue where VSco ...

Is there a way to update the data on a view in Angular 9 without the need to manually refresh the page?

Currently, I am storing information in the SessionStorage and attempting to display it in my view. However, there seems to be a timing issue where the HTML rendering happens faster than the asynchronous storage saving process. To better illustrate this com ...

The module './installers/setupEvents' could not be located within Electron-Winstaller

After encountering an error while attempting to package my Angular app on Windows 10, I'm looking for help in resolving the issue: https://i.stack.imgur.com/yByZf.jpg The command I am using is: "package-win": "electron-packager . qlocktwo-app --ove ...

Saving data from Material UI forms in TypeScript

Is there an effective method for storing values entered into the text fields on this page? const AddUserPage = () => ( <div> <PermanentDrawerLeft></PermanentDrawerLeft> <div className='main-content'> < ...

Unable to establish a connection between the HTML element and the TypeScript variable

I'm facing an issue with my code where the function that worked perfectly for register and login is not functioning properly on the index page. Even though there seems to be no errors in the login and register functions, I have a form with an input s ...

Include type declarations for property values that resemble arrays in a generic object

Imagine you have a function that: receives an object with multiple properties and a property name; checks if the property holds an array; if it does, performs an action (such as printing the values it contains) Here's an illustration: function pro ...

Encountered an issue when attempting to include the "for" attribute to a label within an angular 2.0 template

I am currently using Angular 2.0 framework and working on developing an input component. In addition to that, I am also utilizing Google MDL which requires a specific HTML structure with labels for inputs. However, when trying to implement this, I encounte ...

How to minimize scaffolding with Redux, React, and Typescript?

Is there a way to avoid the process of instrumenting my Redux-Aware components? The level of scaffolding required seems excessive. Take, for instance, the minimal code necessary to define a redux-aware component: class _MyActualComponent extends React.Co ...

Deactivating Google Map Clustering for Individual Markers

I'm currently utilizing Angular 4, Google Maps v3, and Marker Clusterer v2 - all of which are the latest versions. I'm attempting to implement a straightforward example found in the official Google Maps documentation (https://developers.google.co ...

In order to use the serve command, it is necessary to run it within an Angular project. However, if a project definition cannot be located

An error occurred while running the ng serve command: C:\Mysystem\Programs\myfwms>ng serve The serve command needs to be executed within an Angular project, but a project definition could not be found. I encounter this error when ...

Tips for synchronizing response JSON with TypeScript interface in Angular 6

I am retrieving a list of files that have been uploaded from a backend endpoint, and it comes back in this format: [ { "filename": "setup.cfg", "id": 1, "path": C:\\back-end\\uploads\\setup.cfg", ...

Utilizing a string as an argument in a function and dynamically assigning it as a key name in object.assign

Within my Angular 5 app written in TypeScript, I have a method in a service that requires two arguments: an event object and a string serving as the key for an object stored in the browser's web storage. This method is responsible for assigning a new ...

Accessing a data property within an Angular2 route, no matter how deeply nested the route may be, by utilizing ActivatedRoute

Several routes have been defined in the following manner: export const AppRoutes: Routes = [ {path: '', component: HomeComponent, data: {titleKey: 'homeTitle'}}, {path: 'signup', component: SignupComponent, data: {titleKe ...

Implementing a Typescript hook using useContext along with an uninitialized context object

I am currently attempting to develop a custom hook called useAuth in React 17 with TypeScript. While my solution is somewhat functioning, it requires the following syntax when utilizing the hook methods: const auth = useAuth(); // Do other stuff ...

How can I extend a third-party JavaScript library in TypeScript using a declaration file?

Currently, I am working on creating a .d.ts file for an external library called nodejs-driver. While I have been able to map functions and objects successfully, I am struggling with incorporating the "inherit from objects defined in the JS library" conce ...

Determining when to include @types/packagename in React Native's dev dependencies for a specific package

Just getting started with React Native using typescript. Take the package vector icon for example, we need to include 2 dependencies: 1. "react-native-vector-icons": "^7.1.0" (as a dependency) 2. "@types/react-native-vector-icons": "^6.4.6" (as a dev ...

What are the steps to properly build and implement a buffer for socket communication?

I have encountered an issue while converting a code snippet to TypeScript, specifically with the use of a Buffer in conjunction with a UDP socket. The original code fragment is as follows: /// <reference path="../node_modules/DefinitelyTyped/node/node ...

Change the class of <body> when the button is clicked

One of my tasks involves adding a button that, when clicked, should give the body the class "open-menu". Implementing this using jQuery was quite straightforward - I just needed to add the following line of code: $('.burger').click(function() ...