Building off of this question (no I do not have a grand scheme, I simply thought of this after getting my question answered.):
I have the following:
type LengthOfString<T extends string, L extends any[] = []> =
T extends `${string}${infer R}`
? LengthOfString<R, [...L, string]>
: L["length"]
type StringOfLength<T extends string, N extends number> = LengthOfString<T> extends N ? T : never
function isStringOfLength<T extends string, N extends number>(str: T, len: N): str is StringOfLength<T, N> {
return str.length === len
}
function takeString<T extends string>(str: StringOfLength<T, 5>) {
if (!isStringOfLength<T, 5>(str, 5)) throw Error("String length !== 5")
console.log(str)
}
// Works! (fresh)
takeString("22222")
// As expected, errors (fresh)
takeString("2222")
let str = "22222"
// Errors -- how can I avoid this? (str isn't fresh)
takeString(str)
As shown in the code, I have a run-time type guard isStringOfLength
that is meant to throw runtime errors for non-fresh variables. However, I am unsure about how to change my code to accept these non-fresh values, while type-checking against the fresh strings as shown with the first two calls.
CodePudding user response:
If we want to accept
valid literal string types
just the
string
type
but don't want to accept
- invalid literal string types
we can add an additional check to the function parameter.
function takeString<
T extends string
>(str: string extends T ? T : StringOfLength<T, 5>) {
if (!isStringOfLength<T, 5>(str, 5)) throw Error("String length !== 5")
console.log(str)
}
We accept T
as an argument to the function if string
extends T
. But if T
is a string literal, the argument's type must be StringOfLength<T, 5>
. This might seem counter-intuitive as we check if string extends T
before we even infer T
but the compiler makes it work for us.
// Works! (fresh)
takeString("22222")
// As expected, errors (fresh)
takeString("2222")
let str = "22222"
// Does not error anymore
takeString(str)
CodePudding user response:
You can use let str = "22222" as const
to tell TS that str will be the constant string 22222
. But I don't think it'll be terribly useful.
What you need here is to let TS know that str
is a string of length 5. You can use this, but now you have redundant checks, just keep that in mind. str is StringOfLength<T, N>
in the function's return type is the important part here.
let str = "22222"
if (isStringOfLength(str, 5))
takeString(str)