Closed
Description
Bug Report
π Search Terms
type guard conditional type
π Version & Regression Information
- This changed between versions 4.2.3 and 4.3.2
β― Playground Link
Playground link with relevant code
π» Code
interface A<S> {
a: S
}
interface B<S> {
b: S
}
interface C<S> {
c: S
}
type U1<S> = A<S> | B<S> | C<S>
function f1<S>(u: U1<S>): u is B<S> | C<S> {
return false
}
function test1<S>(x: U1<S>) {
if (!f1(x)) {
x.a //OK
}
}
type Cond<S> = S extends number ? B<S> : C<S>
type U2<S> = A<S> | Cond<S>
function f2<S>(u: U2<S>): u is Cond<S> {
return false
}
function test2<S>(x: U2<S>) {
if (!f2(x)) {
x.a //ERROR
}
}
π Actual behavior
The type guard is unable to narrow the type of x
to A
π Expected behavior
The type guard should narrow the type of x
to A
because it is not assignable to Cond<S>
Metadata
Metadata
Assignees
Type
Projects
Milestone
Relationships
Development
No branches or pull requests
Activity
bbenezech commentedon Jun 17, 2021
@RyanCavanaugh Can this bug be prioritize for
4.3.4
please? πIt's a real regression over
4.2
. Inferred types are not narrow-able anymore.RyanCavanaugh commentedon Jun 17, 2021
This doesn't meet our bar for a patch release, but I'll move it to 4.4
andrewbranch commentedon Jul 23, 2021
This was caused by #43183, because the conditional type
Cond<S>
gets substituted for its constraint,B<S> | C<S>
, in the type ofx
before it gets narrowed, so we are effectively trying to narrow fromA<S> | B<S> | C<S>
. Most of the time, thatβs way more useful than trying to narrow withCond<S>
, because it means you can actually discriminate between the two branches of the conditional.Cond<S>
just wonβt relate with many useful types during narrowing. However, it will relate to itself, which is what your example is doing, but in 4.3 we can no longer tell thatβs whatβs happening since we substituted the constraint earlier on.I donβt know if it would make sense for your real code @pedro-pedrosa, but a workaround would be to expand the conditional type to a union in your type predicate:
The only way I can think to fix this without undoing the very useful improvements of #43183 would be to carry the original, non-substituted type through narrowing (in addition to the usually-more-narrowable constraint-substituted type), trying it if narrowing the constraint-substituted type had no effect. But that sounds like it could be expensive, for what feels like quite an edge case to me.
pedro-pedrosa commentedon Jul 26, 2021
Fortunately I was able to change it to
u is A<S>
because in my real example I wasn't exactly trying to figure out if my values whereCond<S>
, I was trying to figure out if they were notCond<S>
. I didn't think of writing the guards this way because my real example is not justA | Cond
, it has a few more types in that union other thanA
so it was just easier to dou is Cond<S>
and negate it.Thanks for the suggestion.
15 remaining items