kfi
kfi

Reputation: 45

Typescript multiple type parameter

I can't figure out how to access the pos propery.

error TS2339: Property 'pos' does not exist on type '{ pos: RoomPosition; } | RoomPosition'.

public moveTo(target: RoomPosition | { pos: RoomPosition }, opts?: FindPathOpts): number {
  ..
  if (target instanceof RoomPosition && !target.isEqualTo(destination.x, destination.y)) {
    ..
  } else if (!target.pos.isEqualTo(destination.x, destination.y)) {
    ..
  }

Upvotes: 4

Views: 7239

Answers (2)

Chris Simon
Chris Simon

Reputation: 6505

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

When you use a union type, you can only access properties that are common to both types - as they are the only properties Typescript knows for sure will be on the variable.

To access the variable that is only available on a given Type, you need to use a type assertion. You can use an explicit type assertion e.g.

if (target instanceof RoomPosition && !target.isEqualTo(destination.x, destination.y)) {
  ...
} else if (<{pos:RoomPosition}>target.pos.isEqualTo(destination.x, destination.y)) {
  ...
}

You should also restructure your code a bit, as the else condition may be triggered even if target is a RoomPosition, but it is not equal to the destination, and that may cause a runtime issue accessing .pos in the else clause. The following is safer:

if (target instanceof RoomPosition) {
    if (!target.isEqualTo(destination.x, destination.y)) {
      ...
    }
} else if (<{pos:RoomPosition}>target.pos.isEqualTo(destination.x, destination.y)) {
  ...
}

It might also be worth defining a named type or interface to represent {pos:RoomPosition}.

There are other syntactic ways of achieving the same thing - see the doc above for user defined type guards and some of the other mechanisms for working with union types.

Upvotes: 6

Nitzan Tomer
Nitzan Tomer

Reputation: 164129

The typescript type guards aren't very smart.
If you'll do this:

if (target instanceof RoomPosition) {
    ...
} else if (!target.pos.isEqualTo(destination.x, destination.y)) {
    ...
}

Then it won't complain about the target.pos, but because your first if contains more logic to it other than the type of target then the compiler doesn't deduce that if that if fails then it means that target is now { pos: RoomPosition } (which is correct as it can fail for another reason).
The next typescript version (2.x) will be smarter, but not sure if it will be better in your case.

How about doing it like this:

public moveTo(target: RoomPosition | { pos: RoomPosition }, opts?: FindPathOpts): number {
    ...

    if (target instanceof RoomPosition) {
        if (!target.isEqualTo(destination.x, destination.y)) {
            ...
        }
    } else {
        if (!target.pos.isEqualTo(destination.x, destination.y)) {
            ...
        }
    }

    ...
}

Upvotes: 2

Related Questions