Consider a union of two types with a type predicate
type Maybe<T> = T | Error
function isValid<T>(value: Maybe<T>): value is T {
return !(value instanceof Error)
}
Lets have code with several computed Maybe values and we want a type predicate which summarily checks all of them to not be an Error:
const a: Maybe<string> = ... // some function returning Maybe<string>
const b: Maybe<number> = ...
...
if (![a, b, /*and more*/].every(isValid)) {
return;
}
const realA: string = a;
//Type 'Maybe<string>' is not assignable to type 'string'.
// Type 'Error' is not assignable to type 'string'.(2322)
Despite the every(isValid) the compiler cannot derive that none of a, b,... is a Maybe any more.
Of course I could write
if (!isValid(a) && !isValid(b) && ...) {
return;
}
to help the compiler along. But I have 5 to 10 as and bs and am in general curious if there is a solution. If have tried
const data = [a, b].filter(isValid)
but this creates a mishmash array type like (string | number)[], so that a data[0] is still not decisively a string.
So neither every nor filter do the trick of allowing the compiler to strip the Maybe afterwards, but I wonder if there is a summarily, "loopy" way which avoids a cumbersome if(!isValid(a)... sequence.
There is currently no way to use a custom type guard function to narrow just the contents or members of a value.
Custom type guard functions, which return a type predicate of the form
arg is Typeorthis is Type, only narrow the apparent type of the specific argument corresponding toargor the object corresponding tothis. Any effects of narrowing would only be seen through subsequent accesses of the identical value. Custom type guard functions do not separately affect the apparent types of values which were assigned to members ofargorthis. For example:In the above, calling
g(x)has the effect of narrowing the apparent type ofxto{a: Date}, and therefore narrowing the apparent type ofx.atoDate. Buta, a reference to the same value asx.a, has not been narrowed.That means it is essentially useless to use a custom type guard function on an anonymous object literal or an array literal:
The object literal
{ a }cannot be accessed again; it's anonymous. Writing{ a }a second time just gives us a new object. And we know thataitself doesn't get narrowed. So we're stuck.There's an open feature request at microsoft/TypeScript#46184 asking for support to narrow object contents (specifically the destructured variables of object literals; presumably for array literals as well), but for now it's not part of the language.
--
You were trying to narrow an array literal
[a, b, ⋯]with the type guard call signature for theevery()method, but as we have just seen, this wouldn't have any effect:Okay, well, what if we assign the array literal to a variable first and access the members through indices later? That fixes the problem with being anonymous:
So, we get narrowing, hooray. Kind of. Unfortunately the array
arris of typeMaybe<string | number | null>[]. So even though we've narrowedarr[0]tostring | number | null, we've completely lost track of the order of the inputs. That brings us to our next problem:To keep track of the order of
arr's elements, we'd need it to have a tuple type instead. Andeverydoesn't act as an element-wise type guard on tuples:And there's no way to fix that. TypeScript currently lacks the expressiveness necessary to describe how to perform element-wise type guards on tuples. It would require some way of talking about an arbitrary generic type
Fat the type level, maybe like:But you can't do that.
Fis a generic type parameter that behaves like a specific type; you can't instantiate it with its own type arguments likeF<U>orF<T[I]>. There is a longstanding open issue at microsoft/TypeScript#1213 but it's not part of the language. There are various ways to try to simulate/encode such types, but they are not user-friendly enough to suggest here.See Mapping tuple-typed value to different tuple-typed value without casts for more details about a similar issue.
So we'll have to give up on using some kind of general type guard mapper that takes
isValidas an input, and just go ahead and hardcodeisValid()in there.If we want to use an array, we can write
and then use it like
Or if we want to use an object, we can write
and then use it like
Playground link to code