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

Tagged union common property types are not narrowed #10976

Closed
farism opened this issue Sep 18, 2016 · 3 comments
Closed

Tagged union common property types are not narrowed #10976

farism opened this issue Sep 18, 2016 · 3 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@farism
Copy link

farism commented Sep 18, 2016

TypeScript Version: 2.0.2-rc

Code

interface MovePayload {
  type: string;
  active: boolean;
}

interface MoveAction {
  kind: 'move';
  payload: MovePayload;
}

interface ChatPayload {
  type: string;
  message: string;
}

interface ChatAction {
  kind: 'chat';
  payload: ChatPayload;
}

type Action = MoveAction | ChatAction

function move(payload: MovePayload) {
  console.log(payload)
}

function reducer({ kind, payload }: Action) {
  switch (kind) {
    case "move":
      return move(payload)
  }
}

reducer({ kind: 'move', payload: { type: 'forward', active: false } })

Expected behavior:
Type of payload argument passed to move function should be MovePayload

Actual behavior:
TS2345: Argument of type 'MovePayload | ChatPayload' is not assignable to parameter of type 'MovePayload'

Of course, doing move(payload as MovePayload) works correctly.

@farism farism changed the title Tagged union common properties are not narrowed Tagged union common property types are not narrowed Sep 18, 2016
@farism
Copy link
Author

farism commented Sep 18, 2016

Actually.. this is user error. I believe the issue lies with doing the destructure in the reducer, i.e.

function reducer({ kind, payload }: Action)

Because I haven't switched on the kind at this point, flow control analysis cannot narrow down the type of payload. The following works fine

function reducer(action: Action) {
  switch (action.kind) {
    case "move":
      return move(action.payload)
  }
}

I'm wondering if there is a way to make destructuring on tagged unions a bit more ergonomic? It seems like something that would be a common pattern, and having to duplicate destructures in each case could be cumbersome.

@mhegazy
Copy link
Contributor

mhegazy commented Sep 20, 2016

once an object is destructed, the compiler can no longer make any assumptions about the relationships between the parts. Doing so requires data-flow analysis and alias tracking which is not trivial tasks.

@felixfbecker
Copy link
Contributor

This would be really nice to have. I find myself often writing type guards inside filter() callbacks

const elements = new Observable<HTMLElement | null>()
elements.pipe(
  filter((element): element is NonNullable<typeof element> => !!element)
)

which is painful if you just want to narrow the type of one property/tuple element, because you need to repeat the type of the other members and cannot use destructuring:

const mouseOvers = new Observable<MouseEvent>()

mouseOvers.pipe(
  withLatestFrom(elements),
  filter((data): data is [typeof data[0], NonNullable<typeof data[1]>] => !!data[1])
)

vs

mouseOvers.pipe(
  withLatestFrom(elements),
  filter(([, element]): element is NonNullable<typeof element> => !!element)
)

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
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

3 participants