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
satisfies changes the generic type, "resolves" it immediately #52394
It seems that using satisfies on a value that is bound to a generic type, in some way resolves the generic type at that place.
constfoo=<Textends1|2>(bar: T)=>{// PROBLEM: a and b should have the same type.consta=barsatisfiesany;// using any type that doesn't use the generic T here creates this problem// ^? 1 | 2constb=bar;// ^? T extends 1 | 2}
Ryan Cavanaugh (@RyanCavanaugh)
For instance, let's say I wanted to write a function that returns the narrowest type possible, but also make sure that the returned type conforms to some more general type.
typeMyType={a: string;b: 'a'|'b'|'c'|'d'|'e';};constfoo=<Textends'a'|'b'|'c'>(bar: T)=>({a: bar,b: bar,})satisfiesMyType;// I should now have the return type be {a: T; b: t},// (but instead it's {a: 'a' | 'b' | 'c'; b: 'a' | 'b' | 'c'}, as if it was resolved immediately)// while also being sure that if any API changes (like MyType) in the future, I will know exactly what needs to change
What's worth noting is that this bug seems to only happen in some scenarios of T extends. Possibly only when T extends a union type, but not sure, it's hard to track down. The following works as expected:
The simplest possible fix that comes to my mind is to just avoid this "resolving" when we are satisfies but that potentially would also have detrimental effects (if I'm not mistaken). We probably should check the target of satisfies against the result of this getNarrowableTypeForReferencebut we shouldn't return that "resolved" type. Since this happens within checkidentifier (before the type is returned to the checkSatisfiesExpressionWorker - it might require some more code juggling to make it work like this. Or maybe it's just a matter of obtaining the exprType without resolving it (when it's generic etc) and returning that from checkSatisfiesExpressionWorker after checking the assignability. Ryan Cavanaugh (@RyanCavanaugh) how does it sound?
Uh, I have no idea. It's going to be sketchy to try to change this because we don't have much real-world coverage on satisfies yet, but if something does need to change it might be better to rip off the band-aid earlier rather than later.
Mateusz Burzyński (@Andarist) just tested the auto types you just mentioned on different versions of ts on the playground. It works correctly on v4.2.3 (type is T extends 1 | 2), but incorrectly on v4.3.5 (type is 1 | 2)
It makes sense since this getNarrowableTypeForReference call has been introduced in #43183 and that was released in 4.3. I assume that this same call is responsible for "resolving" this generic type too eagerly. cc Anders Hejlsberg (@ahejlsberg)
Admittedly, the above is an absolutely contrived example and does not differ from v4.9.5 and v5.0.4 in the Playground. However, this "widening" behavior does not occur in all scenarios. For example, this is fine:
From the docs, my understanding of satisfies is "The new satisfies operator lets us validate that the type of an expression matches some type, without changing the resulting type of that expression" (emphasis mine), hence why I find this behavior confusing.
I just ran into something that feels very similar, where using satisfies causes types to narrow
typeFoo={a: boolean;}constf={a: true};f.a=false;// no problem!constg={a: true}satisfiesFoo;g.a=false;// uh oh//^ Type 'false' is not assignable to type 'true'.
I filed this as #55189, but feel free to close if it's a duplicate
Activity
RyanCavanaugh commentedon Jan 24, 2023
That's weird, but also, what reason would you write this code in the first place? Something of type
Tcan at most least be its constraint.judehunter commentedon Jan 24, 2023
Ryan Cavanaugh (@RyanCavanaugh)
For instance, let's say I wanted to write a function that returns the narrowest type possible, but also make sure that the returned type conforms to some more general type.
Playground
What's worth noting is that this bug seems to only happen in some scenarios of
T extends. Possibly only whenT extendsa union type, but not sure, it's hard to track down. The following works as expected:Playground
Andarist commentedon Jan 24, 2023
It's this call to
getNarrowableTypeForReferencethat "resolves" the type here. The comment within this function also explains why unions are affected here.The simplest possible fix that comes to my mind is to just avoid this "resolving" when we are
satisfiesbut that potentially would also have detrimental effects (if I'm not mistaken). We probably should check the target ofsatisfiesagainst the result of thisgetNarrowableTypeForReferencebut we shouldn't return that "resolved" type. Since this happens withincheckidentifier(before the type is returned to thecheckSatisfiesExpressionWorker- it might require some more code juggling to make it work like this. Or maybe it's just a matter of obtaining theexprTypewithout resolving it (when it's generic etc) and returning that fromcheckSatisfiesExpressionWorkerafter checking the assignability. Ryan Cavanaugh (@RyanCavanaugh) how does it sound?RyanCavanaugh commentedon Jan 24, 2023
Uh, I have no idea. It's going to be sketchy to try to change this because we don't have much real-world coverage on
satisfiesyet, but if something does need to change it might be better to rip off the band-aid earlier rather than later.Andarist commentedon Jan 25, 2023
Interestingly, a similar thing happens for auto types:
judehunter commentedon Jan 25, 2023
Mateusz Burzyński (@Andarist) just tested the auto types you just mentioned on different versions of ts on the playground. It works correctly on v4.2.3 (type is
T extends 1 | 2), but incorrectly on v4.3.5 (type is1 | 2)If that helps
Andarist commentedon Jan 25, 2023
It makes sense since this
getNarrowableTypeForReferencecall has been introduced in #43183 and that was released in 4.3. I assume that this same call is responsible for "resolving" this generic type too eagerly. cc Anders Hejlsberg (@ahejlsberg)flavianh commentedon Feb 6, 2023
I think I stumbled on the same case:
Link to playground
Andarist commentedon Feb 6, 2023
It definitely looks similar, I verified that this
satisfies Box"casts" yourBSgeneric to its constraint (BoxState) and that shouldn't happen.conorbrandon commentedon Apr 14, 2023
Is this another example?
Slightly different because
satisfiesis used on the returned value of a function:Link to Playground
Admittedly, the above is an absolutely contrived example and does not differ from
v4.9.5andv5.0.4in the Playground. However, this "widening" behavior does not occur in all scenarios. For example, this is fine:Link to Playground
From the docs, my understanding of
satisfiesis "The new satisfies operator lets us validate that the type of an expression matches some type, without changing the resulting type of that expression" (emphasis mine), hence why I find this behavior confusing.rimunroe commentedon Jul 28, 2023
I just ran into something that feels very similar, where using
satisfiescauses types to narrowI filed this as #55189, but feel free to close if it's a duplicate
Andarist commentedon Jul 28, 2023
The issue that you filed feels different than this one to me.
satisfieschanges typechecking behavior #60698 as a duplicate of this issue