Take
Take

Reputation: 394

Why are giving a union type to a variable and giving the same type to a parameter so different?

Using the tsc --strict command I get the following error:

error TS2339: Property 'foo' does not exist on type 'T'. Property 'foo' does not exist on type 'Bar'. 16 console.log(obj.foo)

What I don't understand is why I can assign the object literal to obj but get an error using the same property inside the function. Thanks!

type Foo = {
  foo: string
  xyz: string
}

type Bar = {
  bar: string
  xyz: string
}

type T = Foo | Bar

let obj: T = { foo: "foo", xyz: "xyz" }

const sayHello = (obj: T) => {
  console.log(obj.foo)
}

Upvotes: 1

Views: 628

Answers (2)

kaya3
kaya3

Reputation: 51034

The type Foo is more specific than the type Foo | Bar. If you have a box which you can put Foo | Bars, in, then you can put a Foo into that box, no problem. The variable obj and the parameter also named obj are the same in this regard; you can assign either a Foo or a Bar to the variable, and you can call the function with either a Foo or a Bar as the argument.

The difference is when you try to get obj.foo from obj which has type Foo | Bar. The .foo property is only defined on things of type Foo; but the parameter obj isn't necessarily a Foo, it could be a Bar instead, so it doesn't necessarily have a .foo property. That's why you get an error.

But you'll get the same error if you try to do console.log(obj.foo); with a variable of type T outside the function. It has nothing to do with being a parameter or not.

In your specific code, you don't see this error because even though you declared obj to have type T, Typescript knows that you've assigned a Foo to it, so because of control-flow type narrowing the variable is actually of type Foo after the assignment, and will remain as such until the variable is reassigned. If you change the declaration to avoid narrowing with an as T type assertion, then the error will appear as expected:

let obj: T = { foo: "foo", xyz: "xyz" } as T
console.log(obj.foo); // type error

Upvotes: 0

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249546

When you assign to a union you can assign either one of the constituent types from the union. This is why the assignment succeeds

When you try to access a parameter/variable of a union type, you don't actually know which of the union constituent types will actually be in it. It could be wither of them, so typescript considers as safe only access properties that are common to all members of the union. In your example accessing xyz is safe as it exists in both union constituents

You need to use a type guard in order to get the compiler to narrow the type of the parameter to either one or the other constituent types:


const sayHello = (obj: T) => {
  console.log(obj.xyz) // ok common 
  if ('bar' in obj) {
    obj.bar // ok
  } else {
    obj.foo //ok
  }
}

Playground Link

Upvotes: 2

Related Questions