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
Narrow subtype-reduction-prone unions to their narrowest constituent #47731
declareleta: object|any[]|undefinedif(a===undefined){a=[]// I expect this line to have TS understand that a is any[] from now on}elseif(!Array.isArray(a)){thrownewError()}[...a]// complains here because a is object | any[]
[] is both a valid any[]and a valid object. Therefore TS can't narrow away the object. You're mistakenly assuming object means "any non-array object" but it actually means "any object at all". In fact, because object is a supertype of any[], the compiler is well within its rights to completely forget about the any[] part of the union, which it will do in certain cases (called subtype reduction):
In general, having overlapping types in a union is going to give you a bad time.
declareleta: {push?: never}|any[]|undefinedif(a===undefined){a=[]// I expect this line to have TS understand that a is any[] from now on}elseif(!Array.isArray(a)){thrownewError()}
Subtype-reducing unions aren't great but it seems like we could fix the narrowing-on-assignment logic to do a subtype reduction on any resulting union, since that's assured to be sound.
changed the title [-]Type narrowing doesn't work properly following assignment[/-][+]Narrow subtype-reduction-prone unions to their narrowest constituent on assignment[/+]on Feb 4, 2022
changed the title [-]Narrow subtype-reduction-prone unions to their narrowest constituent on assignment[/-][+]Narrow subtype-reduction-prone unions to their narrowest constituent[/+]on Feb 4, 2022
letd: {push?: never}|any[]|undefinedd=[]takeArray(d)// works
Bruce Pascoe (@fatcerberus) thank you for explaining how TypeScript currently sees this scenario but no matter what it does, the point of my issue is that maybe there would be something to change so that it is more ergonomical.
the point of my issue is that maybe there would be something to change so that it is more ergonomical
Indeed, and I agree this is less than ergonomic. Note Ryan Cavanaugh (@RyanCavanaugh)'s renaming of the issue which is one way this could be solved. It's just that you posted this as a bug report while the observed behavior actually falls out naturally from the rules of the type system, so it seemed like a misunderstanding that I was trying to clear up (because fixing this adds yet another ad-hoc rule to type inference/narrowing that's not based purely on assignability, and IMO type inference in TS is way too ad-hoc as it is). Apparently Ryan agrees with you that it's a bug though so... yeah 😛
I think the narrowing could be narrower than anything in the union, for example here it could be narrowed into [] no it seems like that could break a.push()
Activity
fatcerberus commentedon Feb 4, 2022
This is expected; arrays are assignable to
object.paul-marechal commentedon Feb 4, 2022
Bruce Pascoe (@fatcerberus) while it might be the case, I don't see how that makes it any less unexpected?
Or am I wrong to expect that since I explicitly pass an array the typings should narrow to that?
I tried
a = [] as constora = [] as any[]and still get the same issue, it really feels counter-intuitive.fatcerberus commentedon Feb 4, 2022
[]is both a validany[]and a validobject. Therefore TS can't narrow away theobject. You're mistakenly assumingobjectmeans "any non-array object" but it actually means "any object at all". In fact, becauseobjectis a supertype ofany[], the compiler is well within its rights to completely forget about theany[]part of the union, which it will do in certain cases (called subtype reduction):In general, having overlapping types in a union is going to give you a bad time.
RyanCavanaugh commentedon Feb 4, 2022
You could write
Subtype-reducing unions aren't great but it seems like we could fix the narrowing-on-assignment logic to do a subtype reduction on any resulting union, since that's assured to be sound.
[-]Type narrowing doesn't work properly following assignment[/-][+]Narrow subtype-reduction-prone unions to their narrowest constituent on assignment[/+][-]Narrow subtype-reduction-prone unions to their narrowest constituent on assignment[/-][+]Narrow subtype-reduction-prone unions to their narrowest constituent[/+]paul-marechal commentedon Feb 4, 2022
Bruce Pascoe (@fatcerberus) I very much understand this, hence why we can do the following:
What I think isn't ergonomic is not narrowing the type to what's being assigned explicitly:
But Ryan Cavanaugh (@RyanCavanaugh)'s suggestion works!
Bruce Pascoe (@fatcerberus) thank you for explaining how TypeScript currently sees this scenario but no matter what it does, the point of my issue is that maybe there would be something to change so that it is more ergonomical.
fatcerberus commentedon Feb 5, 2022
Indeed, and I agree this is less than ergonomic. Note Ryan Cavanaugh (@RyanCavanaugh)'s renaming of the issue which is one way this could be solved. It's just that you posted this as a bug report while the observed behavior actually falls out naturally from the rules of the type system, so it seemed like a misunderstanding that I was trying to clear up (because fixing this adds yet another ad-hoc rule to type inference/narrowing that's not based purely on assignability, and IMO type inference in TS is way too ad-hoc as it is). Apparently Ryan agrees with you that it's a bug though so... yeah 😛
nimeratus commentedon Aug 24, 2026
I think the narrowing could be narrower than anything in the union,
for example here it could be narrowed intono it seems like that could break[]a.push()