Open shicks opened 5 years ago
declare const x: string;
declare function isString(x: unknown): x is string;
if (!isString(x)) {
//`x` is now of type `never`
//but I want this to compile
//because defensive programming against JS code
throw new Error(`Expected string; received ${typeof x}`);
}
@AnyhowStep He mentioned that, actually.
This is quite different from the always-true case where defensive coding comes into play.
isString()
is always true (per the type system), you're just negating it afterwards.
I don't think it's justifiable that defensive checks are always written in always-true format. You could easily imagine writing:
function fn(x: number) {
if (isNullOrUndefined(x)) Debug.fail("x must be provided");
}
It's not clear what distinguishes this from the case in the OP.
That's a good point, though I suspect it's a much less common case. There's always the option to cast to unknown, though it's not particularly satisfying. It's also less relevant for this to be a type guard, since it's not actually narrowing anything.
@RyanCavanaugh - my underlying motivation was a comment you made about wanting to warn on Number.isFinite(shouldNeverBeANumber)
, which is somewhat similar - I think your example here is more compelling because it's about null/undefined, which we naturally understand to generally be a possible exception to the type system.
Maybe a cleverer approach would be to warn if typeguard(x | undefined | null)
would be always false. It lacks a certain symmetry and in general cleverness is bad, but it seems to pass the gut test a little better...
I suppose one could declare the check as
declare function isNullOrUndefined(x: any): x is null | undefined;
But that doesn't seem useful; when do you ever need a value that's guaranteed to be nullish? And if you're not using it as a type guard... well, then that's what distinguishes it from the OP. The issue is about warning on always-false type predicate calls.
I can't think of a single time I've ever seen a type guard which is expected to always be false in normal operation. Generic defensive boolean
checks, sure, but no always-false type guards.
Search Terms
type guard always-false warning
Suggestion
When a typeguard is always false, the guarded variable is type
never
within the body. While you can't actually look up any properties onnever
, you can pass it anywhere or assign it to anything and there's absolutely no warnings. Calling a typeguard function with an argument that has no overlap with the guard should produce a warning.Use Cases
My immediate motivation is https://github.com/microsoft/TypeScript/pull/24436#issuecomment-392997204, where if
Number.isFinite
could be written as a typeguard (this would also need a narrowernumber
subtype, see e.g. #32188) then it could easily be allowed to be called with anumber|string
, but no additional gymnastics would be required to warn if calling with a guaranteed non-number
.In general there's not a good reason to call an always-false typeguard. This is quite different from the always-true case where defensive coding comes into play.
Examples
Example:
If
usePrivate
requires a private type that this scope doesn't have access to construct, then it would be nice if this code warned somewhere. Instead, it currently just narrowsx
tonever
and allows doing whatever you want with it.Checklist
My suggestion meets these guidelines: