I am writing in TypeScript and use type-fest
for some additional type checking.
The following code:
function type_fest_is_equal_assertion<T, S>(): void {
let R: IsEqual<T, S> = true; // *
console.log(R);
}
… fails at the line commented with the asterisk (*
) with:
Type 'boolean' is not assignable to type 'IsEqual<T, S>'
The error message makes no sense to me. I though that IsEqual
is a type alias that evaluates to a boolean type: true or false. How should I read this error message to make it make sense?
Incidentally, motivation for writing the above function is given here.
IsEqual<T, S>
is implemented via conditional type, so inside the body of your generic function, it is a generic conditional type. Rather than rely on a third-party library I'm going to just make my own version for demonstration purposes:
type Eq<T, S> =
[T] extends [S] ? [S] extends [T] ? true : false : false
function f<T, S>(): void {
let R: Eq<T, S> = true; // error
console.log(R);
}
(No, it's not exactly the same, but it demonstrates the same issue. Feel free to replace.) So Eq<T, S>
will either be true
or false
, depending on T
and S
. Inside the body of f()
, the compiler does not know much else. T
and S
are not known, and so Eq<T, S>
represents some unknown subtype of boolean
. It is not the same as boolean
. If you want a value of type boolean
I can safely give you a value of type Eq<T, S>
, but the reverse is decidedly unsafe. We don't know if Eq<T, S>
will be true
or false
, so we cannot assign true
to it.
For example, what if someone makes the following call?
f<string, number>(); // no error
Now we can see that Eq<T, S>
will be Eq<string, number>
which is false
. So R
would be of type false
and it would be a big mistake to allow assigning true
to it. Of course it is possible to call f<string, string>()
and then R
would be true
, but that's not guaranteed inside the body of f()
.
Even if you constrain the S
and T
generics in the call signature of f()
so that you are sure Eq<T, S>
will be true
and cannot be false
, the compiler is unlikely to be able to perform the same reasoning. Generic conditional types are quite often completely opaque to the compiler, so you'll still get such errors:
function f<
T extends (Eq<T, S> extends true ? unknown : never),
S extends (Eq<T, S> extends true ? unknown : never)
>(): void {
let R: Eq<T, S> = true; // still error
console.log(R);
}
f<string, number>(); // error now
So that's what's going on. An even simpler demonstration is:
function f<R extends boolean>() {
let R: R = true; // error
}
The compiler doesn't know that true
is assignable to R
. All it knows is that R
is assignable to boolean
, which is the opposite direction from what you need. It's almost impossible to safely assign a specific type to a generic one.