Retrieve type definitions for function parameters from an immutable array containing multiple arrays

My current challenge involves implementing a function similar to Jest's test.each iterator:

// with "as const"
forEach([
    [ 1, 2, 3 ],
    [ "a", "b", "c" ],
] as const, (first, second, third) => {
    // ...
});

// without "as const"
forEach([
    [ 1, 2, 3 ],
    [ "a", "b", "c" ],
], (first, second, third) => {
    // ...
});

The main objective here is to ensure that the arguments first, second, and third are strongly typed: without as const, they should all be of type string | number; with as const, they should be respectively 1 | "a", 2 | "b", and 3 | "c". The actual functionality of this function may not be relevant and could even be nonsensical based on its name.

I have made some progress towards achieving the desired typing effect (view in the Playground):

// implementation is not required
declare function forEach<
    Lists extends ReadonlyArray<ReadonlyArray<unknown>>,
>(
    lists: Lists,
    iterator: (...args: Lists[number]) => void,
): void;

Although I considered adopting Jest's typings, their approach seemed messy and fragile, so I decided against it.

The arguments are correctly typed, but there are still compilation errors in both scenarios:

  • with as const:

    The type readonly [1, 2, 3] is 'readonly' and cannot be assigned to the mutable type

    [first: 1 | "a", second: 2 | "b", third: 3 | "c"]

  • without as const:

    Type number[] | string[] is not assignable to type

    [first: string | number, second: string | number, third: string | number]
    . Target requires 3 element(s) but source may have fewer.

Is there a way to define the forEach function to satisfy both use cases?

Answer №1

To accurately identify the types for each case, it is necessary to infer them separately. Below is the code snippet along with explanations provided in the comments. Do let me know if further clarification is required.

declare function forEach<
    Lists extends BaseLists,
>(
    lists: Lists,
    iterator: (...args: TypeUnion<Lists>) => void,
): void;

type BaseLists = ReadonlyArray<ReadonlyArray<unknown>>;

// for readonly lists (when used with as const)
type TypeUnionReadonly<Lists extends BaseLists> = Lists[number] extends readonly [...(infer T)] // infer the tuple type for every list
    ? [...T] // tuple type
    : never

type TypeUnion<Lists extends BaseLists> = Lists[number] extends (infer T)[]
    ? T[]
    : Lists[number] extends ReadonlyArray<any>
    ? TypeUnionReadonly<Lists>
    : never

// ====================
// non const examples
forEach([
    [ 1, 2, 3],
    [ "a", "b", "c" ],
], (first, second, third) => {
    // ...
});

forEach([
    [ 1, 2, 3, 4], // works
    [ "a", "b", "c" ],
], (first, second, third) => {
    // ...
});

// ====================
// const examples
forEach([
    [ 1, 2, 3],
    [ "a", "b", "c" ],
] as const, (first, second, third) => {
   // ...
});

forEach([
    [ 1, 2, 3, 4], // error- length is 4 in one list, also the argument list has 3 parameters
    [ "a", "b", "c" ],
] as const, (first, second, third) => {
   // ...
});

forEach([
    [ 1, 2, 3, 4], // error- length is 4 in one list even if argument list has 4 parameters
    [ "a", "b", "c" ],
] as const, (first, second, third, forth) => {
   // ...
});
forEach([
    [ 1, 2, 3, 4], // works
    [ "a", "b", "c", "d"],
] as const, (first, second, third, forth) => {
   // ...
});

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

Dynamic React Gallery with Interactive Image Picker

Looking to develop a new photo management application as an alternative to Google Photos, with a focus on displaying and selecting images in a user-friendly way. Currently using the react-grid-gallery library for this purpose. Here is my current implement ...

TypeScript primitive type is a fundamental data type within the

Does TypeScript have a predefined "primitive" type or similar concept? Like type primitive = 'number' | 'boolean' | 'string';. I'm aware I could define it manually, but having it built-in would be neat. ...

Embedding a transpiled .js file in HTML using ExpressJS as a static resource

ExpressJS setup to serve transpiled TypeScript files is giving me trouble. Whenever I try to access /components/foo.js, I keep getting a 404 error. /* /dist/server.js: */ var express = require('express'); var app = express(); var path = requir ...

Encountering issues while attempting to run an npm install command on the package.json file

Having trouble running npm install to set up my Angular project on a Mac. It seems like the issues are due to working with an older project. npm ERR! code ERESOLVE npm ERR! ERESOLVE could not resolve npm ERR! npm ERR! While resolving: @angular-devkit/< ...

ElevationScroll expects the 'children' prop to be a single child component of type 'ReactElement<any, string>'

I am currently working on integrating the Elevate AppBar from Material UI into my application using the following code: interface Props { children: React.ReactElement; } export const ElevationScroll = ({children}: Props) => { const trigger = u ...

What issues can trailing white space cause in TypeScript coding?

While I understand that linting is the reason for this, why are trailing spaces considered problematic? I plan to disable this feature in tslint.json, but before I make that change, I want to ensure I'm not making a mistake. Visual Studio Code alert ...

What is the best approach to re-establish a websocket connection in TypeScript?

Encountering an issue when trying to add a "retry()" method: ERROR in src/app/films.service.ts(28,20): error TS2339: Property 'retry' does not exist on type 'WebSocketSubject'. this.wsSubject.retry().subscribe( (msg) => this. ...

Assigning different data types with matching keys - "Cannot assign type '...' to type 'never'."

I have a question regarding my application, where I am utilizing values that can either be static or functions returning those values. For TypeScript, I have defined the static values along with their types in the following manner: type Static = { key1: ...

`mat chip component in Angular Material is malfunctioning`

Whenever I input a string, it does not display properly within the designated textbox. HTML <mat-form-field class="favorite-fruits"> <mat-label>Favorite Fruits</mat-label> <mat-chip-list #chipList aria- ...

What is the best way to kickstart a Reactive Angular 2 form by utilizing an Observable?

My current strategy involves storing the form values in my ngrx store so that users can easily navigate around the site and return to the form if needed. The concept is to have the form values repopulate from the store using an observable. This is how I a ...

Blend the power of Node's CommonJS with the versatility of Typescript's ES modules

I currently have a Node.js v10 legacy application that was built using CommonJS modules (require). The entire codebase is written in JavaScript. However, I am considering upgrading the app and refactoring a specific part of it to use TypeScript modules ( ...

The Context API's `useContext` hook appears to be malfunctioning, persistently

My situation is as follows: export const LocationContext = createContext(null); export const LocationProvider = LocationContext.Provider; export const useLocationContext = () => useContext(LocationContext); Using the Provider: export const Search = () ...

Change the property value prior to running TypeScript validation

I possess the following object: const translations = { msg_hello: 'Hello', msg_bye: 'Bye' } In addition, I have a function that is structured as such: const generateTranslation = (partialKey: string): keyof typeof translations ...

Inject a cookie into the Axios interceptor for the request handler

I am in the process of setting up Axios to always include a request header Authorization with a value from the user's cookie. Here is my code: import axios, { AxiosRequestConfig, AxiosResponse} from 'axios'; import {useCookies} from "react-c ...

Create generic functions that prioritize overloading with the first generic type that is not included in the parameters

I encountered an issue while utilizing generic overload functions, as demonstrated below in the playground. The generic type T1 is solely used in the return type and not the parameters. Therefore, when attempting to use overload #2, I am required to speci ...

Dynamic table row that expands to show additional rows sourced from various data sets

Is there a way to expand the table row in an angular-material table when it is clicked, showing multiple sets of rows in the same column as the table? The new rows should share the same column header but not necessarily come from the same data source. Whe ...

What is the best way to delete markers from a leaflet map?

I need to remove markers from my map. I am looking to create a function that will specifically clear a marker based on its ID. I am utilizing Leaflet for the map implementation. Here is my function: public clearMarkers(): void { for (var id in this. ...

How can I store various data types in a single array in TypeScript?

I have a scenario I need help with. Let's say we have two interfaces, Cats and Dogs. How can I create an array that can store both Cats and Dogs? interface Cats { name: string; age: number; } interface Dog { owner: string; } const cat1: Cat ...

Code error TS2345 occurs when assigning the argument of type '{ headers: HttpHeaders; }' to a parameter of type 'RequestOptionsArgs'. This indicates a mismatch in the type of data being passed, causing an

Upon running ionic serve, these are the results that I am encountering. My setup consists of Ionic4 version with Angular 8. While executing the command, this error appears: src/app/home/home.page.ts:60:77 - error TS2345: Argument of type '{ headers ...

Setting up "connect-redis" in a TypeScript environment is a straightforward process

Currently, I am diving into the Fullstack React GraphQL TypeScript Tutorial I encountered an issue while trying to connect Redis with express-session... import connectRedis from "connect-redis"; import session from "express-session"; ...