What methods can be employed to maintain the integrity of tuple element labels?

Context

In an attempt to enhance code readability and maintainability, I am exploring the replacement of a complex overloaded function with rest parameters using labeled tuple elements.

Original snippet

Here's a simplified version of the existing overloaded function:

function doSomething(arg1: string, arg2: number):void;
function doSomething(arg1: string, arg2: number, arg3: unknown[]):void;
function doSomething(arg1: string, arg2: number, arg3: boolean):void;
function doSomething(arg1: string, arg2: number, arg3: unknown[], arg4: boolean):void {
    // ...implementation
}

The third and fourth arguments are optional, but when provided, their order can be:

arg3: unknown[]
arg3: unknown[], arg4: boolean
arg3: boolean

Tried workaround

To facilitate working with tuples within utility types, I devised a technique of creating populated tuple elements first and then dynamically adjusting their types based on input variables. The goal is to filter out any elements that resolve to never in the end result.

type CalcAdditionArgs<ThirdArgType extends unknown[], FourthArgType extends boolean> = [
    myArg3: ThirdArgType extends [] ? never : ThirdArgType,
    myArg4 : [FourthArgType] extends [boolean] ? FourthArgType extends true ? true : never : never
]

type GetAdditionalArgs<ThirdArgType extends unknown[], FourthArgType extends boolean> = 
    FilterType<CalcAdditionArgs<ThirdArgType, FourthArgType>,  never>

FilterType has been derived from a modified version of tuple filtering utility found at this link

type FilterType<T extends unknown[], U = undefined> = 
    (T extends [] ? 
        [] :
        (T extends [infer H, ...infer R] ?
            ([H] extends [U] ?
                FilterType<R, U> :
                [H, ...FilterType<R, U>]) : 
            T
        )
    );

For clarity, below is the function utilizing these types:

function execute<
    ThirdArgType extends unknown[] = [],
    FourthArgType extends boolean = false
>(
    arg1: string,
    arg2: number,
    ...args:GetAdditionalArgs<ThirdArgType, FourthArgType>
    
): void {
    // implementation here
}

Issue Faced

Upon review, it was noticed that the custom labels assigned to tuple elements were being stripped off by FilterType. An investigation is needed to rectify this behavior while sticking with the current approach.

  1. Can anyone pinpoint why tuple element labels are being erased by FilterType, and is there a solution available within the existing framework?

Alternatively,

  1. Is there a simpler or more effective solution to achieve the desired outcome?

Resolution

Acknowledging assistance received from @captain-yossarian and insights shared by @ford04 in a related Stack Overflow post (), I have reevaluated my method by leveraging rest parameters:

type Append<E extends [unknown], A extends unknown[]> = [...A, ...E]

type GetMyArrayArg<T extends unknown[]> = [myArrayArg: T extends [] ? never : T]
type GetMyBooleanArg<T extends boolean> = [myBooleanArg : [T] extends [boolean] ? T extends true ? true : never : never]


type AddParameter<T extends [unknown], U extends unknown[] = []> =
    T extends [] ? 
        U :
        T extends [infer H] ?
            [H] extends [never] ? 
                U : 
                Append<T, U> :
            U

type GetAdditionalArgs<ThirdArgType extends unknown[], FourthArgType extends boolean> = 
    AddParameter<
        GetMyBooleanArg<FourthArgType>, 
        AddParameter<
            GetMyArrayArg<ThirdArgType>>
        >


type a = GetAdditionalArgs<[], false>; // []
type b = GetAdditionalArgs<[], true>; // [myBooleanArg: true]
type c = GetAdditionalArgs<[string, number], false>; // [myArrayArg: [string, number]]
type d = GetAdditionalArgs<[string, number, Function], true>; // [myArrayArg: [string, number, Function], myBooleanArg: true]

Answer №1

Have you ever wondered why tuple labels disappear from the output? It's a bit of a mystery.

To delve deeper into this topic, check out the official documentation.

The docs state:

They’re purely there for documentation and tooling

My personal hunch is that labels are stripped due to the behavior of the FilterType utility type. When applied, FilterType essentially creates a new tuple without preserving the original labels.

Take a look at this example:

type Labeled = [name: string];

type Infer<L extends Labeled> = L extends [infer First] ? [First] : never

type Result = Infer<Labeled> // [string], no label

Even number indexing doesn't retain labels. For instance, [L[0]] returns [string].

Interestingly, labels seem to stick around when using rest parameters:

type Labeled = [name: string];

type Infer<L extends Labeled> = L extends [infer _] ? [...L] : never

type Result = Infer<Labeled> // [name: string], with label

CalcAdditionArgs, on the other hand, maintains the existing tuple structure.

Despite my efforts, I couldn't find any official guidance on preserving tuple labels.

I speculate that since FilterType creates a brand-new tuple, maintaining the original label order might present challenges. This uncertainty could be why labels are omitted. But again, it's just a theory.

For further insights, you can check out the related pull request.


If desired, you could incorporate labels within FilterType:

type FilterType<T extends unknown[], U = undefined> =
    (T extends [] ?
        [] :
        (T extends [infer H, ...infer R] ?
            ([H] extends [U] ?
                FilterType<R, U> :
                [Batman: H, ...Superman: FilterType<R, U>]) : 
            T
        )
    );

In the extended code snippet, notice how Batman and Superman labels have been added and maintained in the output:

type FilterType<T extends unknown[], U = undefined> =
    (T extends [] ?
        [] :
        (T extends [infer H, ...infer R] ?
            ([H] extends [U] ?
                FilterType<R, U> :
                [Batman: H, ...Superman: FilterType<R, U>]) :
            T
        )
    );

// Additional examples below...

If these solutions don't meet your needs, one workaround is to manually create a union of acceptable tuples. It may be the most foolproof approach.

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

What causes the variation in typing behavior between specifying props directly on a component versus nesting them inside another prop?

Understanding the next component might be a bit tricky, so let's delve into it (Check playground): type Props<T> = { initValue: T, process: (value: T) => T } export const Input = <T,>({ initValue, process, }: Props<T>): ...

Embrace the power of Angular2: Storing table information into

Custom table design Implement a TypeScript function to extract data from an array and populate it into a stylish table. ...

Mastering typing properties in React with TypeScript

Could someone please help me with this issue? I have created a basic react component. interface iRowData{ name: string } export default function ResultsSection(data: iRowData[]) { return <div>Results</div> } When I try to use it in ano ...

Encountering the error message "TypeError: Cannot access property 'Token' of undefined" while compiling fm.liveswitch

The fm.liveswitch JavaScript Software Development Kit (SDK) is designed for use with both clients and your own backend "app server". It functions smoothly in the frontend thanks to webpack and babel. However, the same import statement: import liveswitch fr ...

Tips on dynamically looping the formcontrolname and implementing validation strategies

Looking for a way to validate multiple looping of dynamic formControlName="xxx" in select field. Check out my HTML code: <ul *ngFor="let detaillist of stressli.stresstabdetails;"> <li> <div class="form-container"> ...

When utilizing Jest, the issue arises that `uuid` is not recognized as

My current setup is as follows: // uuid-handler.ts import { v4 as uuidV4 } from 'uuid'; const generateUuid: () => string = uuidV4; export { generateUuid }; // uuid-handler.spec.ts import { generateUuid } from './uuid-handler'; de ...

After upgrading to Angular 15, the Router getCurrentNavigation function consistently returns null

Since upgrading to angular 15, I've encountered a problem where the this.router.getCurrentNavigation() method is returning null when trying to access a state property passed to the router. This state property was initially set using router.navigate in ...

I'm really puzzled as to why they would choose to export in this manner in React

I noticed that several files were exported in a similar manner, and I'm unsure why this specific method was chosen. Could there be any advantages or particular reasons for exporting them like this? index.ts export { default } from './Something& ...

Do not include ChangeDetectionStrategy when creating component

Is it possible to eliminate the default ChangeDetectionStrategy for each component creation? (Please note that I am working with Angular V 10 in a controlled environment for project maintenance) @Component({ xyz, changeDetection: ChangeDetectionStrategy. ...

What mistake am I making in my Typescript + React code that is causing the error "TypeError: {array of objects).map is not a function" to

I have been developing my personal blog using Nextjs and have encountered an issue with rendering posts on two specific pages: Homepage - displays a select few featured posts Posts - displays all posts To mock the data, I have created a PostUtils.tsx fil ...

Issue: The parameter "data" is not recognized as a valid Document. The input does not match the requirements of a typical JavaScript object

I encountered the following issue: Error: Argument "data" is not a valid Document. Input is not a plain JavaScript object. while attempting to update a document using firebase admin SDK. Below is the TypeScript snippet: var myDoc = new MyDoc(); myDo ...

parsing a TypeScript query

Is there a simpler way to convert a query string into an object while preserving the respective data types? Let me provide some context: I utilize a table from an external service that generates filters as I add them. The challenge arises when I need to en ...

When using Vue 3 in my app, I discovered that all the TypeScript files are easily accessible through the browser console

I recently completed my Vue3 js app with Typescript, and I have noticed that all the Typescript files are easily accessible for anyone to view through the Sources tab of the browser console. The code is perfectly clear and readable. Is there a method to p ...

Transforming Form Field Inputs into Model Data Types

Looking at my Angular 8 component, there is a form that allows users to create a post. The PostModel consists of three properties: a string, a boolean, and a number: export interface PostModel { title: string year: number; approved: Boolean; } T ...

Managing HTTP requests with errors within a forEach loop in Angular 9

I am currently coding a script that iterates through an array to make HTTP requests. Most of these requests are directed towards non-existent resources, but I do not have information on which ones specifically. Here is the code snippet I am using: ...

The @IsEnum function does not support converting undefined or null values to objects

When I tried to use the IsEnum class validator in the code snippet below: export class UpdateEvaluationModelForReportChanges { @IsNotEmpty() @IsEnum(ReportOperationEnum) // FIRST operation: ReportOperationEnum; @IsNotEmpty() @IsEnum(Evaluatio ...

Guide to making type-safe web service requests using Typescript

When utilizing Angular for web service calls, it's important to note that the type of the returned object is not automatically verified. For example, let's say I have a Typescript class named Course: export class Course { constructor( publ ...

Production environment experiencing issues with Custom Dashboard functionality for AdminJS

I have successfully integrated AdminJS into my Koa nodejs server and it works perfectly in my local environment. My objective is to override the Dashboard component, which I was able to do without any issues when not running in production mode. However, wh ...

Using async/await does not execute in the same manner as forEach loop

Here is a code snippet that I have been working with. It is set up to run 'one' and then 'two' in that specific order. The original code listed below is functioning correctly: (async () => { await runit('one').then(res ...

What is the best way to access a web.config key using Typescript?

Is there a way to retrieve key values in an Angular 2 application using TypeScript? add key="localPath" value="http://localhost:618/" add key="serverPath" value="http://api.azure.net/" I am looking to access the values of "localpath" and "serverpath" in ...