镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

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

Closed

Description

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

Activity

  1. akarzazi commented on Dec 10, 2016

    @akarzazi

    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
    }
  2. ahejlsberg commented on Dec 10, 2016

    @ahejlsberg
    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
    }
  3. ethanresnick commented on Dec 12, 2016

    @ethanresnick
    ContributorAuthor

    Makes perfect sense. Brain fart.

  4. ocombe commented on Mar 14, 2018

    @ocombe

    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.

  5. donaldpipowitch commented on May 30, 2018

    @donaldpipowitch
    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'
    });
  6. paddotk commented on Jul 30, 2018

    @paddotk

    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?

  7. artola commented on Jun 29, 2019

    @artola

    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

  8. escKeyStroke commented on Aug 30, 2019

    @escKeyStroke

    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'
  9. mookilyu commented on Dec 24, 2019

    @mookilyu

    Any solution?

  10. xxzhong21330 commented on Dec 26, 2019

    @xxzhong21330

    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.

  11. cartok commented on Oct 7, 2020

    @cartok

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

  12. thorn0 commented on Jan 14, 2021

    @thorn0
    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. 🤔🤔🤔
  13. RyanCavanaugh commented on Jan 14, 2021

    @RyanCavanaugh
    Member

    Georgii Dolzhykov (@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.

  14. thorn0 commented on Jan 14, 2021

    @thorn0

    Ryan Cavanaugh (@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).

  15. locked as resolved and limited conversation to collaborators on Oct 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    QuestionAn issue which isn't directly actionable in code

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions