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
Cannot access property that exists only on some union members #36194
Expected behavior:
I can access options.baz and its type is number | undefined.
Actual behavior:
Error: property baz does not exist on type SomeOptions. The workaround does work, but is unnecessarily verbose and does unnecessary runtime checks.
I know this is mentioned in the handbook, but I'd like to advocate for this behavior to be reconsidered. It was mentioned that the type at runtime might be different than expected, but this is also true for the workaround.
I also know I could add all these properties to the other union members (baz?: never), but this is quite tedious and error prone for complex real-world types.
changed the title [-]Cannot access property that exists on some union members[/-][+]Cannot access property that exists only on some union members[/+]on Jan 15, 2020
Joe Calzaretta (@jcalz) The OP there is actually quite similar - but the issue has been closed because the control flow narrowing effects are undesired. My intent here is just to get the non-chained property access.
I've read in other issues that this behavior requires exact types (so we cannot pass a SomeOptions & {baz: boolean} for example). In that case, please put this request on the wishlist for that feature.
Allowing ab.a in all positions would be kind of bonkers. It would imply you could write pure nonsense like ab.a?.toLowerCase() + ab.x?.toFixed()
So maybe you say that ab.a is only allowable sometimes. But where?
Restricting it to "inside conditionals" is just an immediate "Bug: TypeScript is iNcOnSiStEnT about where I can read this property" bug farm; there are also positions which are ambiguously conditional or not (e.g. left side of | / || / & / &&)
Restricting it to a more "obvious intent" form "a" in ab is what we have today
But maybe there's something that would make sense?
Allowing ab.a in all positions would be kind of bonkers. It would imply you could write pure nonsense like ab.a?.toLowerCase() + ab.x?.toFixed()
I disagree that that is bonkers. The question mark makes it clear that either the left or right term might be undefined. Yes, we are loosing the fact that precisely one of them might be undefined, but that seems like a fair tradeoff.
For example if you allow access to anything defined in the union, but as optional properties:
type A = { x: number, y: number };
type B = { a: string, b: string };
declare const ab: A | B;
type LaxAB = Partial<A> & Partial<B>
const laxAb: LaxAB = ab;
laxAb.a?.toLowerCase() + laxAb.x?.toFixed()
You will still get an error Object is possibly 'undefined'. because the left and right terms might be undefined.
And my case with optional props of React component:
// Workaround typetypeObjectUnionOptionalPropsFix<O1extendsobject,O2extendsobject>=|O1&{[OptKeysinkeyofOmit<O2,keyofO1>]: undefined}|O2&{[OptKeysinkeyofOmit<O1,keyofO2>]: undefined}typeType={type: 'type1'|'type2'}typeCategory={category: 'category1'|'category2'}typeTypeOrCategory=Type|Category// React componentconstRenderTypeOrCategory=(props: TypeOrCategory)=>{// Without workaround❌: TS2339: Property type does not exist on type TypeOrCategory// Without workaround❌: TS2339: Property category does not exist on type TypeOrCategory// With workaround✅: type: 'type1' | 'type2' | undefined// With workaround✅: category: 'category1' | 'category2' | undefinedconst{ type, category }=propsasObjectUnionOptionalPropsFix<Type,Category>if(type)return<>{/* Render type via type-consuming component */}{/* Correct✅: type: 'type1' | 'type2' */}{type}</>return<>{/* Render category via category-consuming component */}{/* Correct✅: category: 'category1' | 'category2' */}{category}</>}
Activity
[-]Cannot access property that exists on some union members[/-][+]Cannot access property that exists only on some union members[/+]jcalz commentedon Jan 15, 2020
Related to the declined #33736.
AlCalzone commentedon Jan 15, 2020
Joe Calzaretta (@jcalz) The OP there is actually quite similar - but the issue has been closed because the control flow narrowing effects are undesired. My intent here is just to get the non-chained property access.
I've read in other issues that this behavior requires exact types (so we cannot pass a
SomeOptions & {baz: boolean}for example). In that case, please put this request on the wishlist for that feature.RyanCavanaugh commentedon Jan 15, 2020
This has been the behavior for about five years now; #1260 would be the issue to follow if you expect changes.
AlCalzone commentedon Jan 15, 2020
Ryan Cavanaugh (@RyanCavanaugh) is it realistic to expect such a change (for example in combination with exact types) at some point in the future?
RyanCavanaugh commentedon Jan 16, 2020
We'd have to think about it. Just hypothesizing, let's say you had
What would be the ideal rules here?
ab.ain all positions would be kind of bonkers. It would imply you could write pure nonsense likeab.a?.toLowerCase() + ab.x?.toFixed()ab.ais only allowable sometimes. But where?|/||/&/&&)"a" in abis what we have todayAlCalzone commentedon Jan 16, 2020
I have no good answer for that. The example you posted is clearly much more complicated than my simple use case of a single property access.
typescript-bot commentedon Jan 18, 2020
This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.
patrickjcurran commentedon Apr 19, 2023
I disagree that that is bonkers. The question mark makes it clear that either the left or right term might be undefined. Yes, we are loosing the fact that precisely one of them might be undefined, but that seems like a fair tradeoff.
For example if you allow access to anything defined in the union, but as optional properties:
You will still get an error
Object is possibly 'undefined'.because the left and right terms might be undefined.AlCalzone commentedon Apr 19, 2023
Patrick Curran (@patrickjcurran) the error is because you're trying to concatenate possibly undefined values:
patrickjcurran commentedon Apr 19, 2023
AlCalzone understood, my point was that it is good that the type checker doesn't allow that. I'll edit my post to make that more clear. Thank you.
rrain0 commentedon Mar 3, 2025
It is not an ultimate solution, but i've created a workaround that can cover some cases. Hope this helps someone:
And my case with optional props of React component: