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.
CodePudding user response:
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"
CodePudding user response:
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.
CodePudding user response:
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'
CodePudding user response:
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, string]
) {
const [age, maybeName] = args; // [number, string | undefined]
}
assertPerson(2, "hey");
assertPerson(0)