Reputation: 432
We are using Knockout.js (v3.5.0) and its TypeScript definitions. They worked OK until TypeScript 4.6.2. However the problem seems to be "deeper" than in the definitions file. It seems that there was some change in TypeScript in handling a boolean type. So rather than tagging this question as Knockout.js problem, I created small example of code inspired by the Knockout d.ts that illustrates the problem:
interface Observable<T>
{
(): T;
(value: T): any;
}
function observable<T>(value: T): Observable<T>
{
return undefined as any; // the implementation is not important
}
const x: Observable<boolean> = observable(false);
This code has one compilation problem:
Type 'Observable<false>' is not assignable to type 'Observable<boolean>'.
Types of parameters 'value' and 'value' are incompatible.
Type 'boolean' is not assignable to type 'false'.
Of course casting the false
as boolean
works, but I do consider this as hack, not a solution (obviously we need cast on every occurrence of true/false).
Any way to actually solve this?
Edit: based on comment it is obvious that some type checking was changed. More examples can be seen here. Playground Link.
Is there any info (with explanation) for this change?
Edit2: as suggested in comments, I filed a bug report at https://github.com/microsoft/TypeScript/issues/48150
Upvotes: 2
Views: 1381
Reputation: 432
According to the user who has responded to the Github issue (https://github.com/microsoft/TypeScript/issues/48150) the Typescript 4.6 compilation error is expected:
I believe this is a correct error which was not handled properly in old versions. The generic parameter T is invariant as it is used in both a covariant position () => T and a contravariant position (value: T) => any.
which is indeed true. Since user helped solve the problem, for sake of completeness I will try to rephrase and summarize his comments here.
The first proposed solution solves the problem only partially:
function observable<T>(value: T extends infer U ? U: never): Observable<T>
{
return undefined as any; // the implementation is not important
}
const x: Observable<boolean> = observable(false); // no error
however that will produce unknown
type without a type annotation.
const bad = observable(false);
// ^ unknown, which should be Observable<boolean>
The correct solution seems to introduce another type parameter
declare function observable<T, U = any>(value?: T extends infer R ? R : U):
unknown extends T ? Observable<U> : Observable<T>
As the main issue was with the Knockout.js library, I filed the issue there (https://github.com/knockout/knockout/issues/2589), hopefully someone with enough Typescript knowledge will correct the problem.
Upvotes: 3
Reputation: 91
I would suggest to instead explicitly specifying the generic type like:
const x = observable<boolean>(false);
The reason for this is that we are assuming that false
implies boolean
. Usually it does, but not always. Imagine e.g. these types
const x = observable<false | undefined>(false);
const y = observable(false);
(For anybody trying to solve the typing problem in any other way, please make sure that the above lines are also valid.)
Upvotes: 3