When dealing with a conditional type that relies on a generic type parameter, such as RT<T>
, the compiler defers evaluation until the generic type argument is specified. Consequently, the compiler lacks knowledge of what can or cannot be assigned to RT<T>
prior to determining the value of T
.
This situation persists even if T
is constrained to a type fitting exclusively in the true or false branch of the conditional type. You might anticipate that constraining T
to Array<unknown>
would allow the compiler to evaluate
T extends Array<unknown> ? { v: T } : T
as
{ v: T }
before specifying
T
, but this behavior does not occur.
Although the syntax for generics constraints <T extends U>
mirrors the syntax for conditional type checks T extends U ? X : Y
, the compiler is unable to leverage the former to early-evaluate the latter.
There have been discussions on GitHub regarding this issue, like microsoft/TypeScript#31096 and microsoft/TypeScript#56045. These topics are typically closed citing design limitations. Therefore, the language operates in this manner for the foreseeable future.
Hence, within the call signature for test()
, the compiler does not recognize that {v: T}
can be assigned to RT<T>
. Subsequently, it prohibits returning value is RT<T>
because type predicates must signify narrowings rather than arbitrary type mutations.
If you wish to proceed with a function of this nature, you will need to adjust the call signature. In cases where you know that type U
is assignable to type
V</code while the compiler remains unaware, you can employ the <a href="https://www.typescriptlang.org/docs/handbook/2/objects.html#intersection-types" rel="nofollow noreferrer">intersection</a> <code>U & V
instead of
U
. The compiler acknowledges that
U & V
is always assignable to
V</code irrelevant of <code>U
. If your assumption about assignability holds, then
U & V
essentially equates to
U
. (Another option could involve using
Extract<U, V>
with the
Extract
utility type). This permits the following:
function test<T extends Array<unknown>>(value: RT<T>): value is RT<T> & { v: T } {
return "v" in value;
}
and successfully compiles. Proceeding should now be feasible.
Moreover, considering your actual use case likely motivates the type guard function. However, based on face value, it may appear redundant. Attempting to aggressively assess RT<T>
by claiming it must be {v: T}
due to T
's constraint to
Array<unknown></code, results in:</p>
<pre><code>function test<T extends Array<unknown>>(value: { v: T }): value is { v: T } {
return "v" in value;
}
This implies that you would execute test(value)
to confirm that value
aligns with a known type. In essence, this function can never return false
, and the necessity or rationale behind checking this remains unclear. While this example illustrates modifying a type predicate for acceptance, its practical purpose seems questionable. Nevertheless, addressing this falls beyond the scope of the initial question, and further elaboration will not be pursued.
Check out the Playground link for code