Reputation: 5425
I have a function that takes a second argument which is only required when the first argument is a string matching a template literal string. However, it appears that even when TypeScript knows that the first argument is such a string, it still matches the overload that takes a regular string:
type KnownString = `known${string}`;
type AnyString = string;
function doSomethingWithString(arg1: KnownString, onlyRequiredForKnownString: boolean): string;
function doSomethingWithString(arg1: AnyString, onlyRequiredForKnownString?: boolean): string;
function doSomethingWithString(arg1: KnownString | AnyString, onlyRequiredForKnownString?: boolean): string {
return "The second argument is only required when `arg1` is a KnownString";
}
const thisIsAKnownString = "knownstring" as KnownString;
// @ts-expect-error Since TS knows that arg1 is a KnownString, it should require the second parameter:
doSomethingWithString(thisIsAKnownString);
This makes sense of course, since it is a regular string. Is there a way to tell that overload to match any regular string except KnownString
?
(I did try with a conditional type like suggested in this similar question, but that does not appear to work with template literal strings.)
Upvotes: 1
Views: 295
Reputation: 5425
I did find another solution with function overloads and somewhat more straightforward types:
type KnownString = `known${string}`;
type AnyString = string;
type NotKnown<T extends AnyString> = T extends KnownString ? never : T;
function doSomethingWithString(arg1: KnownString, onlyRequiredForKnownString: boolean): string;
function doSomethingWithString<T extends AnyString>(arg1: T & NotKnown<T>): string;
function doSomethingWithString(arg1: KnownString | AnyString, onlyRequiredForKnownString?: boolean): string {
return "The second argument is only required when `arg1` is a KnownString";
}
const thisIsAKnownString = "knownstring" as KnownString;
// @ts-expect-error Since TS knows that arg1 is a KnownString, it should require the second parameter:
doSomethingWithString(thisIsAKnownString);
Upvotes: 0
Reputation: 33051
I believe you dont need to overload your function.
Consider next example:
type IsKnown<T extends string> = T extends `known${string}` ? true:false
type Validator<T extends boolean> =
T extends true
? []
: [boolean];
function validation<Str extends string>(arg1: Str, ...validation: [...Validator<IsKnown<Str>>]): string {
return "The second argument is only required when `arg1` is a KnownString";
}
const knownstring = "knownstring";
validation(knownstring); // does not require second argument
validation('unknown'); // requires second argument
Please keep in mind, that function foo(a:number,...args:[]){}
evaluates to function foo(a:number){}
MOre information about this approach you can find in my blog
Take into account, that you can compose your validators:
type IsKnown<T> = T extends `known${string}` ? true : false
type StringNumber<T> = T extends `${number}` ? true : false
type Validator<T extends boolean> =
T extends true
? []
: [boolean];
function validation<Str extends string>(arg1: Str, ...validation: Validator<StringNumber<Str>> | Validator<IsKnown<Str>>): string {
return "The second argument is only required when `arg1` is a KnownString";
}
const knownstring = "knownstring";
validation(knownstring); // does not require second argument
validation('2234'); // does not require second argument, because it is number
validation('wer'); // requires second argument, because it is number
Upvotes: 2