Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot access property that exists only on some union members #36194

Closed
AlCalzone opened this issue Jan 15, 2020 · 10 comments
Closed

Cannot access property that exists only on some union members #36194

AlCalzone opened this issue Jan 15, 2020 · 10 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@AlCalzone
Copy link
Contributor

AlCalzone commented Jan 15, 2020

TypeScript Version: Nightly

Search Terms: Union property does not exist

Code

interface SomeOptions {
    foo: boolean;
}

interface OtherOptions {
    bar: boolean;
    baz?: number;
}

function doSomething(options: SomeOptions | OtherOptions) {
    // Error:
    const baz1 = options.baz ?? 1;
    // Workaround:
    const baz2 = ("baz" in options ? options.baz : undefined) ?? 1;
}

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.

Playground Link: https://www.typescriptlang.org/play/?ts=3.8.0-dev.20200114&ssl=1&ssc=1&pln=15&pc=2#code/JYOwLgpgTgZghgYwgAgMoHsC2EDyAHMYdEAZ2QG8AoZG5GddALmQCMGAbCOEAbkoF9KlUJFiIUOMAAto+QsTJVarOFGZt0nbn2Us4ALwD8zEAFdMLaH0GUYpkAnkhkAE3QZs00AHMAFOgIiUmYPXECFZAAfZEkZKDkgkgBKCmpaAHp05ABRKCh0NTSaBAUwFX0ARmQAXmQApxIAOj19ZENDZAqdDKyAdQKAaxJGIuQS0jKWgCYa5F8AIhb55FA68NI2tYbmg2RmexcIGFAIFxT2zusgA

Related Issues:

@AlCalzone AlCalzone changed the title Cannot access property that exists on some union members Cannot access property that exists only on some union members Jan 15, 2020
@jcalz
Copy link
Contributor

jcalz commented Jan 15, 2020

Related to the declined #33736.

@AlCalzone
Copy link
Contributor Author

@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 RyanCavanaugh added the Working as Intended The behavior described is the intended behavior; this is not a bug label Jan 15, 2020
@RyanCavanaugh
Copy link
Member

This has been the behavior for about five years now; #1260 would be the issue to follow if you expect changes.

@AlCalzone
Copy link
Contributor Author

@RyanCavanaugh is it realistic to expect such a change (for example in combination with exact types) at some point in the future?

@RyanCavanaugh
Copy link
Member

We'd have to think about it. Just hypothesizing, let's say you had

type A = exact { x: number, y: number };
type B = exact { a: string, b: string };
declare const ab: A | B;

What would be the ideal rules here?

  • 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?
  • (Something else?)

@AlCalzone
Copy link
Contributor Author

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
Copy link
Collaborator

This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

@patrickjcurran
Copy link

patrickjcurran commented Apr 19, 2023

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.

@AlCalzone
Copy link
Contributor Author

@patrickjcurran the error is because you're trying to concatenate possibly undefined values:

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;

const test1 = laxAb.a?.toLowerCase();
const test2 = laxAb.x?.toFixed()

test1 + test2; // Error here!

@patrickjcurran
Copy link

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

5 participants