Creating a TypeScript function that utilizes generics to automatically infer the return type

How can I create a function with a generic argument that can return any type, and have the return type inferred from its usage?

I attempted the following code:

type Thing<T> = <U>(value: T) => U

const shouldMakeStrings: Thing<string> = (a: string) => a + 'foo'
const shouldMakeObjects: Thing<string> = (a: string) => ({ foo: a })

I expected the types of shouldMakeStrings to be (a: string) => string and shouldMakeObjects to be

(a: string) => { foo: string }

However, I encountered an error stating that

'U' could be instantiated with an arbitrary type which could be unrelated to 'string'

Is there a way to define the Thing type without specifying U, but still allow for any return type?


Below is a more comprehensive example demonstrating what I'm trying to achieve (playground link):

type Thing<T> = <U>(value: T) => U

// Example, this will be a big map of custom types and string representations
type Types = {
  'string': string,
  'number': number
}

type CallbackMap = {
  [P in keyof Types]?: Thing<Types[P]>
}

const callbacks: CallbackMap = {}

const addCallback = <V>(type: keyof Types, callback: Thing<V>) => {
  callbacks[type] = callback
}

addCallback('string', (a: string) => a + 'foo')
addCallback('number', (a: number) => { foo: a })

callbacks.string // = callback that takes a string and returns something, with it's type inferred as string from `addCallback('string')` above
callbacks.number // = callback that takes a number and returns something, with it's type inferred as '{ foo: number }' from `addCallback('number')` above

Here's a more verbose example, although it has other TypeScript-related issues (playground):

type A = { foo: string }
type B = { bar: string }
type C = number[]

type Modifiable = {
  'typeA': A,
  'typeB': B,
  'typeC': C
}

type Modifier<T> = <U>(value: T) => U
type ModifierMap = {
  [P in keyof Modifiable]?: Modifier<Modifiable[P]>
}

const modifiers: ModifierMap = {
  'typeA': (a: A) => a
}

const setModifier = <V extends keyof Modifiable = keyof Modifiable>(type: V, callback: Modifier<Modifiable[V]>) => {
  modifiers[type] = callback
}

setModifier('typeA', (input: A) => input.foo + 'a')

setModifier('typeA', a => a.foo)

// With the aim to be able to do something like this:
const makeModifiedA = (modifiers?: ModifierMap) => {
  const a = { foo: "hello world" }

  return modifiers?.typeA ? modifiers.typeA(a) : a
}

makeModifiedA(modifiers) // = "hello world"
setModifier('typeA', (input: A) => ({ ...input, upperFoo: input.foo.toUpperCase() }))
makeModifiedA(modifiers) // = { foo: "hello world", upperFoo: "HELLO WORLD" }

Answer №1

When using TypeScript, you have the choice to assign a type or let it be inferred, but not both simultaneously.

If you want to ensure that a function meets certain criteria without affecting its type inference, you can employ a workaround. This involves performing a constraint check before the type is finalized.

const satisfies = <Constraint>() => <Input extends Constraint>(input: Input) => input;

const checked = satisfies<(arg: string) => any>()(arg1 => {
  console.log(arg1);
  return 2;
});

Interestingly, there is an ongoing proposal to introduce this functionality as a built-in feature in TypeScript known as the satisfies operator.

https://github.com/microsoft/TypeScript/issues/47920

Therefore, in future versions of TypeScript (4.9 and beyond), you may see syntax like:

const checked = ((arg1: string) => 2) satisfies (arg: string) => any

Answer №2

Is it possible for any/unknown to be a match?

type Item<T> = (data: T) => unknown

const generateStrings: Item<string> = (str: string) => str + 'bar'
const generateObjects: Item<string> = (str: string) => ({ bar: str })

Playground

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

"Troubleshooting: Why Angular2's ngOnChanges is not triggering with array input

I am currently using a ParentComponent to pass inputs to a ChildComponent. When the input is a number, the ngOnChanges hook successfully fires. However, when it's an array, the hook does not trigger. Could someone help me identify what I might be doi ...

Utilizing React forwardRef with a functional component

Looking at my code, I have defined an interface as follows: export interface INTERFACE1{ name?: string; label?: string; } Additionally, there is a function component implemented like this: export function FUNCTION1({ name, label }: INTERFACE1) { ...

An error occurred while attempting to set up Next-auth in the process of developing

In my Next.js app, I have implemented next-auth for authentication. During local development, everything works fine with 'npm install' and 'npm run dev', but when I try to build the project, I encounter this error message: ./node_modul ...

Learn how to define an array of member names in TypeScript for a specific type

Is there a way to generate an array containing the names of members of a specific type in an expression? For example: export type FileInfo = { id: number title ?: string ext?: string|null } const fileinfo_fields = ["id","ext&qu ...

Exploring the synergy between Visual Studio 2015 and Angular2 Beta 2's dependency injection functionality

Currently, I am using VS2015 alongside TypeScript 1.7.3 and Angular2 Beta 2 with a target of ECMA 5. In order to enable experimental decorators in my project, I have made modifications to the csproj file by including TypeScriptExperimentalDecorators = true ...

How can you make sure that VS Code always utilizes relative paths for auto imports in TypeScript?

VS Code has been automatically importing everything using Node-like non-relative paths relative to baseUrl, which is not the desired behavior. Is there a way to instruct VS Code to import everything with relative paths, excluding Node modules? Removing t ...

Testing Angular: How to Unit-test HttpErrorResponse with custom headers using HttpClientTestingModule

In the code snippet below, I am attempting to find a custom header on error: login(credentials: Credentials): Observable<any> { return this.http.post(loginUrl, credentials) .pipe( catchError((httpErrorResponse: HttpErrorRespo ...

Deciding between bundling a Typescript package and using tsc: When is each approach the best choice

When it comes to publishing a Typescript NPM package (library, not client), I have two main options: 1. Leveraging Typescript's compiler First option is to use the Typescript compiler: tsc and configure a tsconfig.json file with an outDir setting: { ...

TS2339 Error: The object 'Navigator' does not contain the property 'clipboard'

In the project I'm working on, there is an error that comes up when trying to copy custom data to the clipboard. It's something I can easily put together. Error TS2339: Property 'clipboard' does not exist on type 'Navigator' ...

Make sure to incorporate the .gitignored files that are compiled from typescript when running the `npm install -g

Looking for a way to ignore the JavaScript files compiled from TypeScript in my git repository to make merging, rebasing, and partial commits easier. Here's how I have it set up: tsconfig.json { "compilerOptions": { "outDir": "./dist" ...

Is Angular File API Support Compatible with HTML5?

When checking for HTML5 File API browser support in my code: private hasHtml5FileApiSupport; constructor(@Optional() @Inject(DOCUMENT) document: Document) { const w = document.defaultView; this.hasHtml5FileApiSupport = w.File && w.FileReader & ...

Error message in VsCode plugin stating that property 'X' is not found on type '{}' within a Vue 3 template

Currently, I am in the process of setting up my vue-cli project that utilizes the composition API with <script setup> to fully integrate TypeScript. Each time I try to use variables within template tags, VSCode flags errors. I have already installed ...

Issue with Angular Swiper carousel: Error message pointing to node_modules/swiper/angular/angular/src/swiper-events.d.ts

I am attempting to implement a carousel in Angular using Swiper (). An error message is appearing: Error: node_modules/swiper/angular/angular/src/swiper-events.d.ts:3:50 - error TS2344: Type 'SwiperEvents[Property]' does not meet the constraint ...

How can we dynamically render a component in React using an object?

Hey everyone, I'm facing an issue. I would like to render a list that includes a title and an icon, and I want to do it dynamically using the map method. Here is the object from the backend API (there are more than 2 :D) // icons are Material UI Ic ...

The onChange event will not be triggered in an input component that is not intended to be uncontrolled

Could someone please assist me in understanding why the onChange event is not being triggered? I am customizing ChakraUI's Input component to retrieve a value from localStorage if it exists. The component successfully retrieves the value from localS ...

Steps to access email template through an Excel web add-in

Currently, I am developing a new addin that aims to extract data from Excel and insert it into a word document. The final step would be attaching this document to an email in Outlook. While I have managed to achieve this using Power Automate, I prefer to ...

Prevent the page from closing by implementing a solution within the ionViewWillLeave method

I'm looking to use the ionViewWillLeave method to prevent the page from closing and instead display a pop-up confirmation (using toast.service) without altering the form. ionViewWillLeave(){ this.toast.toastError('Do you want to save your cha ...

WebStorm's TypeScript definitions are failing to function properly

I'm experiencing an issue with my three.js code and TypeScript definitions in settings. Despite enabling them, there doesn't seem to be any effect. I've downloaded everything and checked the necessary boxes, but nothing is changing. WebStorm ...

Tips for improving the slow compilation of the Next.js 14 development environment

Currently, I am tackling an issue with my Typescript - Next.js 14 Application where the compilation process in the development environment is taking excessive time, sometimes up to 60 seconds. What steps can be taken to resolve this problem and optimize t ...

Importing Angular libraries with the * symbol

One of the key modules in my library is sha256. To import it, I had to use the following syntax: import sha256 from 'sha256'; However, while researching this issue, I came across a question on Stack Overflow titled: Errors when using MomentJS ...