Reputation: 2839
Typescript supports Conditional Types.
But when I try to set value of op
as string, it gives me error, how do I check the type and assign value?
export const t = <IsMulti extends boolean = false>(): void => {
const value = 'test';
type S = string;
type D = IsMulti extends true ? S[] : S;
const op: D = value;
console.log(op);
};
Error: Type 'string' is not assignable to type 'D'.
I have even tried adding a parameter of same type IsMulti
and adding check based on it
export const t = <IsMulti extends boolean = false>(isMulti: IsMulti): void => {
const value = 'test';
type S = string;
type D = IsMulti extends true ? S[] : S;
if (!isMulti) {
const op: D = value;
console.log(op);
}
};
still it gives the same error.
Upvotes: 4
Views: 283
Reputation: 328262
Evaluation of conditional types which depend on as-yet unspecified generic type parameters is generally deferred by the compiler; if the compiler doesn't know exactly what IsMulti
is, then it does not know exactly what IsMulti extends true ? S[] : S
is, and so it won't let you assign a value of type (say) S
to that conditional type, because it is unable to verify that this is a type-safe operation. The compiler doesn't even really try to evaluate such conditional types; it leaves them as opaque things that almost nothing can be assigned to.
For your first example this is the desired behavior from the compiler; it really isn't safe to assign a string
to op
because D
may well be string[]
. Nothing stops someone from calling t<true>()
. Just because IsMulti
has a default of false
doesn't mean it is false
.
For your second example, this is currently a limitation of TypeScript. The compiler cannot use control flow analysis to narrow type parameters. And therefore it cannot verify assignability to a See microsoft/TypeScript#33912 for details.
When you check if (!isMulti)
, the compiler narrows the type of the isMulti
inside the subsequent code block from the IsMulti
type to just false
:
export const t = <IsMulti extends boolean = false>(isMulti: IsMulti): void => {
if (!isMulti) {
// isMulti is now known to be false
const fls: false = isMulti; // okay
const tru: true = isMulti; // error
}
};
But it does not narrow the type parameter IsMulti
itself. In general this is also desired behavior from the compiler, since IsMulti extends boolean
implies that IsMulti
might actually be the full union boolean
(as opposed to just true
or just false
) and so checking a value of type IsMulti
would not be useful to narrow IsMulti
itself. All you could say with if (!isMulti)
is that IsMulti
could not be just true
... but it might still be boolean
. Of course if it is boolean
, then D
, a distributive conditional type would become the union S[] | S
, and you should be allowed to assign a string
to that. So there's no plausible way that assigning "test"
to op
should be unsafe.
And yet, the compiler is unable to verify this. It stubbornly leaves IsMulti
alone, and won't let you assign anything to D
because it defers evaluation. It would be nice if there were some supported way to narrow type parameters, or to verify assignability to generic conditional types. For now, there really isn't. The above linked GitHub issue is a feature request to improve this, and there are a bunch of related requests that might help (e.g., microsoft/TypeScript#27808 could restrict IsMulti
to be exactly true
or exactly false
, and presumably then if (!isMulti)
would narrow the previously generic type IsMulti
to the specific type false
, and D
would be evaluated eagerly). You could go to these issues and give them a 👍, but it's not obvious that anything is going to happen here anytime soon.
Instead, in situations like this where you know more about the types than the compiler does, you can use a type assertion to suppress assignability errors:
export const t = <IsMulti extends boolean = false>(isMulti: IsMulti): void => {
const value = 'test';
type S = string;
type D = IsMulti extends true ? S[] : S;
if (!isMulti) {
const op = value as D; // okay
console.log(op);
}
};
By writing value as D
you're telling the compiler that you know that value
is definitely of type D
, even though the compiler cannot. This allows the program to compile without error. Note that this is not magic, and by doing a type assertion you are taking some of the responsibility for type safety away from the compiler. If you have made a mistake or lied to the compiler, it can't always catch it:
if (!isMulti) {
const op = ["oopsie"] as D; // still okay
console.log(op);
}
So be careful!
Upvotes: 3