is.numericString type guarding returns `x is string` and messes up typescript
Xananax opened this issue · comments
Use case:
const parseValue = (/**@type {string} */value) => {
if (is.numericString(value)) {
return parseFloat(value);
}
if (value.toLowerCase() === "true" || value.toLowerCase() === "false") {
return value.toLowerCase() === "true";
}
//...
}
Typescript assumes value is a string
and then casts value
to never
:
I'm not sure if this is solvable, because strictly speaking the type guard is accurate.
Workaround (for anyone who needs it): recast the value manually:
JS:
value = /**@type {string}*/(value)
TS:
value = value as string
Notably, this recasting is possible in an early return context like my example, but not in an if...else
or switch
context, so it is still a bit of a bother.
The recasting is also surprising and requires a comment for other teammates that may come across it.
Maybe we could make the type guard more specific using a technique like this: https://github.com/sindresorhus/type-fest/blob/0b78096186dfe255b888513d60538e17e35828ea/source/internal.d.ts#L108-L111
Oh that sounds pretty good and seems like an easy change. Want me to PR this?
Use case:
const parseValue = (/**@type {string} */value) => { if (is.numericString(value)) { return parseFloat(value); } if (value.toLowerCase() === "true" || value.toLowerCase() === "false") { return value.toLowerCase() === "true"; } //... }Typescript assumes
value is a string
and then castsvalue
tonever
:I'm not sure if this is solvable, because strictly speaking the type guard is accurate.
Workaround (for anyone who needs it): recast the value manually:
JS:
value = /**@type {string}*/(value)TS:
value = value as stringNotably, this recasting is possible in an early return context like my example, but not in an
if...else
orswitch
context, so it is still a bit of a bother.The recasting is also surprising and requires a comment for other teammates that may come across it.
Hi!
The problem is that in a predicate function, the type guard must always be accurate: if the predicate on a given value returns true, the value must be of the given type in the type guard AND if the predicate returns false, the value must not be of the given type in the type guard.
And this is not the case in the is.numericString predicate. So the solution is indeed to modify the type guard to use a type representing all the numeric string values.
I confirm that changing the signature of numericString
like so:
var numericString: (value: unknown) => value is `${number}`;
fixes the problem.