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

Accessing property in union of object types fails for properties not defined on all union members #12815

Closed
ethanresnick opened this issue Dec 9, 2016 · 14 comments
Labels
Question An issue which isn't directly actionable in code

Comments

@ethanresnick
Copy link
Contributor

TypeScript Version: 2.1.1

Code

type A = { a: string; } 
type B = { b: string; }
type AorB = A | B;

declare const AorB: AorB;

if (AorB.a) {
    // use AorB.a
}

Expected behavior:
The code compiles without errors. Even though a doesn't (necessarily) exist on all the constituents of the union, the fact that it exists on some should allow me to check for .a and use it if it is present.

Actual behavior:
Typescript complains: "Property a does not exist on type AorB... Property a does not exist on type B."

@akarzazi
Copy link

The doc says :

To get the same code working, we’ll need to use a type assertion:

let pet = getSmallPet();

if ((<Fish>pet).swim) {
    (<Fish>pet).swim();
}
else {
    (<Bird>pet).fly();
}

http://www.typescriptlang.org/docs/handbook/advanced-types.html

Then in your sample:

if ((<A>AorB).a) {
    // use AorB.a
}

@ahejlsberg
Copy link
Member

The issue here is that because B doesn't declare an a property, it might at run-time have an a property of any possible type (because you can assign an object with any set of properties to a B as long as it has a b property of type string). You can make it work explicitly declaring an a: undefined property in B (thus ensuring that B won't have some random a property):

type A = { a: string; } 
type B = { b: string; a: undefined }
type AorB = A | B;

declare const AorB: AorB;

if (AorB.a) {
    // Ok
}

@ethanresnick
Copy link
Contributor Author

Makes perfect sense. Brain fart.

@ocombe
Copy link

ocombe commented Mar 14, 2018

If you define a: undefined on type B, you'll have to set it to undefined when you create/pass a variable.
To get around that, you can declare a as a?: undefined, and typescript will be happy if you omit it.

@donaldpipowitch
Copy link
Contributor

If I understand the comments correctly, this should work (but throws; tested on playground): Is this a bug or not? 🤔

type LinkProps = {
    to: string;
    onClick?: undefined;
    // Link specific props:
    target: string;
}

type ButtonProps = {
    to?: undefined;
    onClick: Function;
    // Button specific props:
    disabled: boolean;
}

type ActionProps = LinkProps | ButtonProps;

const Action = (props: ActionProps) =>
    props.to ?
        'Link with target: ' + props.target // Error not on ButtonProps
    :
        'Button with disabled: ' + props.disabled; // Error: not on LinkProps

Action({
  to: 'dssd',
  target: '_blank'
});

@paddotk
Copy link

paddotk commented Jul 30, 2018

I don't understand this at all. Isn't if ((<A>AorB).a) the same as using if (A.a) because you're forcing it back to type A?

@artola
Copy link

artola commented Jun 29, 2019

If you define a: undefined on type B, you'll have to set it to undefined when you create/pass a variable.
To get around that, you can declare a as a?: undefined, and typescript will be happy if you omit it.

might be better a?: never, now you can not assign undefined by mistake

@escKeyStroke
Copy link

What if you don't give a name to each of the members of the union? (can't make a type assertion as above without a name for the asserted type, can I?)

type u = "str" | {prop:"val"};
function f(arg:u){return arg.prop} // TypeScript: Property 'prop' does not exist on type 'u'

@mqliutie
Copy link

Any solution?

@xxzhong21330
Copy link

xxzhong21330 commented Dec 26, 2019

i find a solution on book :

interface A { x:  number;} 
interface B { y:  string;}
function doStuff ( q: A | B ) {
  if ( 'x'  in q) {
    //  if type A...
  }
  else {
    // if type B...
  }
}

Excerpt From: Basarat Ali Syed. “TypeScript Deep Dive.” Apple Books.

a property on an object and can be used as a type guard, and the TypeScript can know which type you used.

@cartok
Copy link

cartok commented Oct 7, 2020

The issue here is that because B doesn't declare an a property, it might at run-time have an a property of any possible type (because you can assign an object with any set of properties to a B as long as it has a b property of type string). You can make it work explicitly declaring an a: undefined property in B (thus ensuring that B won't have some random a property):

type A = { a: string; } 
type B = { b: string; a: undefined }
type AorB = A | B;

declare const AorB: AorB;

if (AorB.a) {
    // Ok
}

Does not work for me 07.10.2020

And I think that this behavior of TS is not good because it does not help a developer much...

@thorn0
Copy link

thorn0 commented Jan 14, 2021

  1. "The issue here is that because B doesn't declare an a property, it might at run-time have an a property of any possible type" (Accessing property in union of object types fails for properties not defined on all union members #12815 (comment))
  2. At the same time, 'a' in AorB is supported as a type guard (Accessing property in union of object types fails for properties not defined on all union members #12815 (comment))
  3. 🤔🤔🤔

@RyanCavanaugh
Copy link
Member

@thorn0 yes, because if you write code like

if (a.foo) {

you might be thinking you're testing for the non-zeroness of foo in one branch of the union, but hitting a plain object through an aliased value in another branch. There's no ambiguity what you're trying to do with "foo" in a.

@thorn0
Copy link

thorn0 commented Jan 14, 2021

@RyanCavanaugh Thanks for the answer, but I'm having trouble understanding it. Could you put it another way please?

upd: Turns out there is an open issue for this: #1260. But I still don't understand your point about ambiguity. if ("foo" in a) is clearly as unsound as if (a.foo).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests