3

Is it possible to have a conidial required argument type based on the first argument type:

type ConditionalRequiredArg<T, P> = T extends string ? P | undefined : P;

function func<T, P>(_arg1: string | number, _arg2: ConditionalRequiredArg<T, P>) {}

func('foo'); // Error: Expected 2 arguments, but got 1.

In theory, the above function should not require a second argument, but it does!

Edit: I am aware of '?' for optional argument types, but I would like to make it conditional to leverage the compiler error and suggestions.

cyrus-d
  • 749
  • 1
  • 12
  • 29
  • 1
    Why do you define an optional type and expect an optional argument behaviour from the function? You can make the argument optional by putting a ? at the end of the argument as follows: function func(_arg1: string | number, _arg2?: ConditionalRequiredArg) – ProgrammerPotato Oct 01 '21 at 06:29
  • it's more of curiosity and trying possibilities. i know i can use the '?' to make it optional, but i would like it to be conditional so the user knows when a second argument is required. – cyrus-d Oct 01 '21 at 07:02

4 Answers4

2

You can always use the signature overloading to screen users from directly using the implementation signature:

function func<P>(_arg1: string, _arg2?: P)
function func<P>(_arg1: number, _arg2: P)
function func<P>(_arg1: string | number, _arg2?: P) { }

This code forces user to choose either the first signature or the second to use, and not the third signature of the implementation.

func('foo', 1); // success
func('foo'); // success
func(1, 1); // success
func(1); // Error: Argument of type 'number' is not assignable to parameter of type 'string'
daylily
  • 876
  • 5
  • 11
1

The first solution to making the second argument of the function defined would be to mark it optional using the ? operator and have the following:

// ...
function func<T, P>(_arg1: string | number, _arg2?: ConditionalRequiredArg<T, P>) {}
// ...

However, if you want to make more changes take a look at the following:

/*
 * You could remove the conditional type to get a better inference on the generics
 * that are used and simply check the typeof _arg1 and if it is string as you had
 * defined in the ConfitionalRequiredArg it will use a type in the generic arg
 * you would then have to provide
 */
function func<P>(_arg1: string | number, _arg2?: typeof _arg1 extends string ? P | undefined : P) {}

func<string>('foo', 4); // ts error 4 is not assignable to type string | undefined

Essentially in the end, to make an argument optional, use the ? operator.

Playground

bloo
  • 1,416
  • 2
  • 13
  • 19
1

I think you are mixing up two different things.

func<T, P>(_arg1: string | number, _arg2: ConditionalRequiredArg<T, P>)

means that the function expects 2 arguments, where the second one might have the type undefined, meaning:

_arg2 = undefined, where the field _arg2 is already declared.

But you want the field to be not declared.

_arg2 = undefined is not equivalent of saying:

func<T, P>(_arg1: string | number, undefined)

For this use case, you can use ? as follows:

func<T, P>(_arg1: string | number, _arg2?: ConditionalRequiredArg<T, P>)

Edit:

Types and arguments are two different things. The type defines the type of the value, whilst an argument is a field.

Although it is not a good practice, you could also initialize the field manually with undefined as follows:

func<T, P>(_arg1: string | number, _arg2: string = undefined)

But this is in the end same as _arg2?

The type undefined is like saying "The field does not contain anything" or better say, "The field contains undefined", whilst the field undefined is "The field does not exist"

ProgrammerPotato
  • 505
  • 1
  • 4
  • 11
  • Thanks for the suggestion i am aware of '?' for optional arguments type, but i was hoping to make it conditional and leverage the compiler error and suggestion. – cyrus-d Oct 01 '21 at 07:07
1

As @daylily said, if you are able to use the signature overloading syntax, then that's definitely the way to go. However, if you need to infer generics then I would usually conditionally generate a tuple:

function assertPerson<
    Age extends number
>(
    ...args: 0 extends Age ? [age: Age] : [age: Age, name: string]
) {
    const [age, maybeName] = args; // [number, string | undefined]
}

assertPerson(2, "hey");
assertPerson(0)

TypeScript Playground Link

sno2
  • 3,274
  • 11
  • 37