You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Type 'X<false>' is not assignable to type 'X<boolean>'. #48150
interfaceObservable<T>{(): T;(value: T): any;}functionobservable<T>(value: T): Observable<T>{returnundefinedasany;// the implementation is not important}constx: Observable<boolean>=observable(false);
🙁 Actual behavior
Type 'Observable<false>' is not assignable to type 'Observable<boolean>'.Typesofparameters 'value' and'value'areincompatible.Type'boolean'isnotassignabletotype'false'.
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.
whzx5byb Suppose this is correct behavior, can you suggest how to continue to use the sketched concept? The interface in fact represents the core concept of Knockout.js library.
To set value to x - use x(val).
To read value from x - use x().
If x is defined to operate on boolean, It is very illogical that I can not set true or false as val. Of course I can cast to boolean, but is it the correct way? And is it the only way?
If x is defined to operate on boolean, It is very illogical that I can not set true or false as val. Of course I can cast to boolean, but is it the correct way? And is it the only way?
A way to make your example work:
functionobservable<T>(value: Textends infer U ? U: never): Observable<T>{returnundefinedasany;// the implementation is not important}constx: Observable<boolean>=observable(false);// no error
tomhanax perhaps this should be referred to the knockout project so that the necessary changes can be made there.
Done: knockout/knockout#2589, hope that somebody there will pick that up, because the project seem to go to winter sleep for good.
The answer in #48150 (comment) is not perfect, as it will produce unknown without a type annotation.
constbad=observable(false);// ^ unknown, which should be Observable<boolean>
The issue here is the inference T extends infer U ? U : never infers U from the LHS of assignment. But when there is none, the default constraint type unknown will be used.
Here is an alternative workaround which will produce the same behavior without a type annotation, but a bit more verbose. I have to introduce two type parameter. T is for the refered type which is from LHS. U is for the "raw" widening type, which will be used only when T is unknown.
declarefunctionobservable<T,U>(value: Textends infer R ? R : U): unknownextendsT ? Observable<U> : Observable<T>;
I'm not sure if there is a better solution. If you find some please let me know.
Michael Best (@mbest), the "most updated" playground (see also knockout/tko#168 (comment)) contains many examples and seems OK, of course anybody can check and correct if needed, I am no expert in this.
I am not saying anything about what "should" be fixed, I am no expert at all. Simply trying to help to solve existing issue, because now we are forced to remain on TS 4.5 until this is solved.
constw=observable(false);// Typescript identifies 'w' as Observable<boolean>constx: Observable<boolean>=observable(false);// Type being Observable<false> now
Typescript seems to change it's mind of what type observable(false) actually is.
Martin Skogevall (@maskmaster) to be honest I am not sure. I only know that what worked previously does not work now. As whzx5byb pointed out, the whole thing may be "reversed" - it was working because of less perfect previous TS version that allowed this and now it is not working because simply it should not work in the first place. I am not good at type theory at all, so anybody else have to say funded words.
Anyway, I am reopening this in hope for clarification.
Before 4.6 we'd measure variance of Observable<T> wrong:
declareletxb: Observable<boolean>;declareletxf: Observable<false>;xb=xf;// Error, but wasn't before 4.6xf=xb;// Error
The type parameter T is used in both input and output positions within Observable<T>, so clearly Observable<T> is invariant and our current behavior is correct. We got it wrong before because we'd cross-relate the overloaded signatures, which sometimes makes sense for different source and target types, but never makes sense when source and target are instantiations of the same type (which they are when we're measuring variance). Not only was it wrong to cross-relate them, it also generated a lot more work, which was the problem fixed in #47341.
Martin Skogevall (@maskmaster) Hmm, yeah, that is a known design limitation, and I agree it's unfortunate. See my comment here. The workaround is to write:
it is producing the intended Observable<boolean>. There is no contextual type for the false argument and therefore we widen the type to boolean. Just like we would widen 42 to number.
Bluntly said this is absolutely silly. In year 2022 you can not possibly tell this to the "average programmer".
Anyway, what is the problem with "more verbose" typings illustrated in the beforementioned playground? I am only asking because that "simply works", but to be true, I really do not understand the magic there.
Thank you Anders Hejlsberg (@ahejlsberg) for the explanation. It makes sense if it a design limitation. I'll try to wrap my head around this later on so I understand exaxtly what happens.
Thanks for linking the other issue.
Edit: it took too long for me to write on the phone. It was already linked.
Activity
whzx5byb commentedon Mar 7, 2022
I believe this is a correct error which was not handled properly in old versions.
The generic parameter
Tis invariant as it is used in both a covariant position() => Tand a contravariant position(value: T) => any.tomhanax commentedon Mar 7, 2022
whzx5byb Suppose this is correct behavior, can you suggest how to continue to use the sketched concept? The interface in fact represents the core concept of Knockout.js library.
To set value to
x- usex(val).To read value from
x- usex().If
xis defined to operate onboolean, It is very illogical that I can not settrueorfalseasval. Of course I can cast toboolean, but is it the correct way? And is it the only way?whzx5byb commentedon Mar 7, 2022
A way to make your example work:
tomhanax commentedon Mar 7, 2022
whzx5byb Just perfect, thanks!
I do not know if it is appropriate here, but if you want, you could post this as answer on https://stackoverflow.com/questions/71319489/type-observablefalse-is-not-assignable-to-type-observableboolean (there is a small bounty if you care). Or I could do it using the code you proposed if you have no interest. Please give me the info, I will close this bug then.
bennieswart commentedon Mar 7, 2022
tomhanax perhaps this should be referred to the knockout project so that the necessary changes can be made there.
tomhanax commentedon Mar 7, 2022
whzx5byb commentedon Mar 7, 2022
tomhanax I don't care, you could post it yourself.
fatcerberus commentedon Mar 7, 2022
For the record, the reason for the type error:
whzx5byb commentedon Mar 7, 2022
tomhanax
The answer in #48150 (comment) is not perfect, as it will produce
unknownwithout a type annotation.The issue here is the inference
T extends infer U ? U : neverinfersUfrom the LHS of assignment. But when there is none, the default constraint typeunknownwill be used.Here is an alternative workaround which will produce the same behavior without a type annotation, but a bit more verbose. I have to introduce two type parameter.
Tis for the refered type which is from LHS.Uis for the "raw" widening type, which will be used only whenTis unknown.I'm not sure if there is a better solution. If you find some please let me know.
maskmaster commentedon Mar 8, 2022
Cross-posting here, just for the record. Make sure that also both these lines are valid with any new proposal.
mbest commentedon Mar 12, 2022
tomhanax I think this should be fixed in TypeScript. The "fixes" for the Knockout code don't make much sense.
Consider that this code has the correct result:
But this one errors:
Also that this works:
But not this:
Playground Link
tomhanax commentedon Mar 14, 2022
Michael Best (@mbest), the "most updated" playground (see also knockout/tko#168 (comment)) contains many examples and seems OK, of course anybody can check and correct if needed, I am no expert in this.
Playground Link
I am not saying anything about what "should" be fixed, I am no expert at all. Simply trying to help to solve existing issue, because now we are forced to remain on TS 4.5 until this is solved.
maskmaster commentedon Mar 17, 2022
tomhanax why was the issue closed? Isn't this a valid bug in Typescript?
As Michael Best (@mbest) pointed out:
Typescript seems to change it's mind of what type
observable(false)actually is.tomhanax commentedon Mar 17, 2022
Martin Skogevall (@maskmaster) to be honest I am not sure. I only know that what worked previously does not work now. As whzx5byb pointed out, the whole thing may be "reversed" - it was working because of less perfect previous TS version that allowed this and now it is not working because simply it should not work in the first place. I am not good at type theory at all, so anybody else have to say funded words.
Anyway, I am reopening this in hope for clarification.
4 remaining items
RyanCavanaugh commentedon Mar 17, 2022
Bisected to #47341. Anders Hejlsberg (@ahejlsberg) this seems like an unintended consequence?
ahejlsberg commentedon Mar 18, 2022
Before 4.6 we'd measure variance of
Observable<T>wrong:The type parameter
Tis used in both input and output positions withinObservable<T>, so clearlyObservable<T>is invariant and our current behavior is correct. We got it wrong before because we'd cross-relate the overloaded signatures, which sometimes makes sense for different source and target types, but never makes sense when source and target are instantiations of the same type (which they are when we're measuring variance). Not only was it wrong to cross-relate them, it also generated a lot more work, which was the problem fixed in #47341.maskmaster commentedon Mar 18, 2022
Anders Hejlsberg (@ahejlsberg) I totally agree with the correctness of your example and that the new behavior as you describe is correct.
I think we are talking about two different things.
In this simple example
According to Typescript 4.6 'w' is an
Observable<boolean>.Still, it is not possible to do (exactly what typescript suggests)
According to you, what is the correct type of 'w' in
const w = observable(false)?ahejlsberg commentedon Mar 18, 2022
Martin Skogevall (@maskmaster) Hmm, yeah, that is a known design limitation, and I agree it's unfortunate. See my comment here. The workaround is to write:
ahejlsberg commentedon Mar 18, 2022
Martin Skogevall (@maskmaster) Regarding the type of
it is producing the intended
Observable<boolean>. There is no contextual type for thefalseargument and therefore we widen the type toboolean. Just like we would widen42tonumber.tomhanax commentedon Mar 18, 2022
Bluntly said this is absolutely silly. In year 2022 you can not possibly tell this to the "average programmer".
Anyway, what is the problem with "more verbose" typings illustrated in the beforementioned playground? I am only asking because that "simply works", but to be true, I really do not understand the magic there.
typescript-bot commentedon Mar 20, 2022
This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.
ahejlsberg commentedon Mar 21, 2022
Yeah, as I said, it's unfortunate. I'll open an issue on this specifically and we'll see if we can do better.
maskmaster commentedon Mar 21, 2022
Thank you Anders Hejlsberg (@ahejlsberg) for the explanation. It makes sense if it a design limitation. I'll try to wrap my head around this later on so I understand exaxtly what happens.
Thanks for linking the other issue.
Edit: it took too long for me to write on the phone. It was already linked.