Analysis
I designed an interface that takes two type parameters, with the second parameter being optional and defaulting to void. Additionally, I created a utility type called CommandReturnType
which employs conditional typing to ensure that void is not returned if the second type parameter is not provided.
interface Command<IOType, AdditionalOutputType = void> {
execute(input: IOType): CommandReturnType<IOType, AdditionalOutputType>;
}
type CommandReturnType<A, B> = B extends void ? A : A | B;
To further enhance this setup, I developed a utility type implementing infer
in order to return a tuple containing the two type parameter types.
type GetCommandParamTypes<T extends Command<unknown, unknown>> =
T extends Command<infer IO, infer A> ?
[IO, A] :
never;
Testing revealed that when providing the utility type with an interface having only one type parameter, it correctly identifies void as the second parameter.
type type1 = GetCommandParamTypes<Command<string>>; // [string, void]
However, when supplying a class that implements the interface with just one type parameter, it results in both slots of the tuple containing the same type.
class TypeACommand implements Command<string> {
execute(input: string): string {
return input;
}
}
type type2 = GetCommandParamTypes<TypeACommand>; // [string, string]
Solution Attempt
In exploring potential solutions, I noticed that using Exclude<>
could eliminate duplicate types from the second slot of the tuple.
type GetCommandParamTypes<T extends Command<unknown, unknown>> =
T extends Command<infer IO, infer A> ?
[IO, Exclude<A, IO>] :
never;
This approach worked effectively when assigning a type for the second type parameter:
class MixedTypeCommand implements Command<string | number, Function> {
execute(input: string | number): string | number | Function {
// do something with input here...
return something;
}
}
type type3 = GetCommandParamTypes<MixedTypeCommand>; // [string | number, Function] instead of [string | number, string | number | Function]
Unfortunately, occurrences where Exclude<type type>
equals never resulted in issues.
type type4 = GetCommandParamTypes<TypeACommand>; // [string, never]
Subsequently, I attempted to address this by replacing never with void, but encountered setbacks.
type ReplaceNever<T> = T extends never ? void : T;
type GetCommandParamTypes<T extends Command<unknown, unknown>> =
T extends Command<infer IO, infer A> ?
[IO, ReplaceNever<Exclude<A, IO>>] :
never;
type type5 = GetCommandParamTypes<TypeACommand>; // [string, never]
Query
Is there a straightforward method to replace occurrences of never?
If not, are there alternative solutions available to eliminate duplicate types from the second tuple slot and substitute nothingness with void if necessary?
Thus, achieving:
type wrong = GetCommandParamTypes<TypeACommand>; // [string, string]
transformed into:
type right = GetCommandParamTypes<TypeACommand>; // [string, void]