TS optional and required generic union types

Viewed 309

Goal

I am trying to create an optional parameter unless a type has been supplied making it required.

Expected Behaviour

I want the following get method to have an optional parameter by default. If the get method is called and provided a type as TT, the method should then have a required parameter of type TT.

export class Test<T = void> {

    get<TT>(param: T & TT): Test<T & TT>
    get<TT>(param: void | TT): Test<T & TT>
    get<TT>(param: T & TT): Test<T & TT> {
        return null as any;
    }

}

const test = new Test();

test.get();

test
    .get<{ name: string }>({ name: '' })
    .get() // Expected 1 arguments, but got 0.
    .get<{ age: number }>({ age: 23 }); // Property 'name' is missing in type '{ age: number; }' but required in type '{ name: string; }'

The code above will compile, but I want it to throw the errors mentioned in the comments. Any idea how I can achieve this?

1 Answers

UPDATE 20 September 2021

It requires a bit of typescript gymnastic.

Consider this example.

Explanation is in comments

class Test<T = void> {

    /**
     * If T is void (is not provided)
     * we dont expect any arguments,
     * hence 
     * 
     * But if T is provided - expect T as an argument
     * and return Test<T> respectfuly
     */
    get<U extends {
        0: 'T is void',
        1: 'T is provided'
    }[T extends void ? 0 : 1]>(
        ...param: (
            U extends 'T is provided'
            ? [T]
            : []
        )
    ):
        U extends 'T is provided' ? Test<T> : Test
    /**
     * If T extends void - argument should
     * have U type
     * If T provided and it is pseudo equal U - expect T & U
     * If T provided and it is a subtype of U - expect T
     * If T is provided and it is not a subtype of U - expect T & U
     */
    get<U>(
        ...param: (T extends void
            ? [U]
            : T extends U
            ? (U extends T
                ? [T & U]
                : [T]
            )
            : [T & U]
        )
    ): Test<U>
    /**
     * Default types
     */
    get<U>(...param: Partial<U[]>) {
        return null as any
    }

}

const test = new Test(); // ok

test.get() // ok

const result = test
    .get<{ name: string }>({ name: '' }) // ok
    .get() // error Expected 1 arguments, but got 0.
    .get<{ age: number }>({ age: 23 }); // error

Playground

I made my own assumptions about certain requirements. If it does not meet your requirements, please provide more into your question.

For now, this answer technically meets your goal

Related