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

Union types aren't always narrowed properly after predicate functions #35299

Closed
dplumlee opened this issue Nov 22, 2019 · 5 comments
Closed

Union types aren't always narrowed properly after predicate functions #35299

dplumlee opened this issue Nov 22, 2019 · 5 comments
Labels
Duplicate An existing issue was already created

Comments

@dplumlee
Copy link

TypeScript Version: 3.8.0-dev.20191122

Search Terms:
union types, narrowing, guarding
Code

// Imagine redux here. Redux reducers get actions from various places. They have a vague type
type Action = {
  type: any
}

// Our app has 2 actions
type ActionA = {
  type: 'a'
  payload: () => void
}

type ActionB = {
  type: 'b'
  payload: string
}

// This type is a union of all of em. They have distinct types, which can 
// be used to discriminate them
type AllOurActions = ActionA | ActionB

// This function takes any action, and determines if it happens to
// be one of ours. If so, it narrows the action's type
declare function isActionOneOfOurs(action: Action): action is AllOurActions

// This is how you could narrow our action type to a specific action type
declare const wereSureAlready: AllOurActions
// This guard usually works
if (wereSureAlready.type === 'a') {
  // Since the `type` property is 'a', we know the payload is a function
  // since 'b' actions have a string payload
  wereSureAlready.payload()
}

// We got this action in our reducer.
// It could be from anywhere(e.g.redux dev tools or a third party lib)
declare const someGivenAction: Action

// If this is true, we know the action is one of ours
if (isActionOneOfOurs(someGivenAction)) {
  // can be narrowed by switch
  switch (someGivenAction.type) {
    case 'a': someGivenAction.payload()
  }
  // can't with other stuff?
  if (someGivenAction.type === 'a') {
    // Why doesn't this work if someGivenAction was narrow by a predicate
    // function. 
    someGivenAction.payload()
  }

  // it works if you rebind it
  const worksIfYouRebindIt = someGivenAction
  if (worksIfYouRebindIt.type === 'a') {
    // Why doesn't this work if someGivenAction was narrow by a predicate
    // function. it works if you cast it
    worksIfYouRebindIt.payload()
  }
}

Expected behavior:
Action type properly narrowed by the predicate function and an if statement

Actual behavior:
Action type not narrowed unless you rebind it prior to narrowing or a switch statement is used

Playground Link:
http://www.typescriptlang.org/play/?ssl=1&ssc=1&pln=58&pc=2#code/PTAEEkFsEMHMEsB2BTUAnZATArgD1ABbIYB0oASlnulQMbEDOosyALqNLa-APaJMAzND0igAbtDS9sTAA4AbTsgZkAKkQCehaGNTRxcbKlYbZyAFAmzoAIJdeiUAF5QAb3OhQV5AC4OiDXMAX3NzEFAAeWw0DllZbSYAJg57PgZLU1Q7bj4bZzcPL0y-AHJoEsLZaA15HmhMPwAKAEpnAD5xHnhMYNDvW1TEACF8909vUoAjCs8qmrqG0AZWKURYXrCwdXgmfp2OUGxEB1AeAQ55eVPz5Eg1TW1dUEwd7kQuIrMGABpQAHcCPBaARQLRoI5NqBJqgZFgvDxnjtaFJIEhoKxjERIBlrDZLlE0NkHEwXETcqAAD4DHLDULhbaCI6DLzQADWyn8Wk4NN+4MwzzYxFRKCY8HO8HYBGgcWQ-HhkOhpxQ11O0RUEHODB4vwloEQkmEf12RBSNJKu0y5kwyFoigwoAETJpoB2ZMQERQEQEBIYjW5Dj8buafn9fBdTDx8gJbvSkIZ4cIPD+oA0PGwoLT8n5+rQhtVMVDjn6rAR+gYZloYqBppO3itNrtqFoaXYf2IyAAytFkHiMPUNIH8dEY3HAUxYNhJPyZJPLlo-jw0Kz0mLQI02xguxhe8h+yR+k5D6AyiVWmNQOEO0h6F4TQADbx30CyYRmNAmBMn35t0CsxBJ29UDmWp6gTfRHXeGlCnCBhr1QEpphrNJHj0JYViQWBn2qECek8DdO27Hc92AhYWg2cIAHVUFgHh2FYMckMcJB8xoHB6DQEhIXAdhm2wLMoVQIQRE5AF20aZASFgEgMBwfBrTEeEeHkJhFwOej4DQfkqnfLR5HgSZmnrW1JCbFslhEZAAHF4F0RA3UDQY6TAcBznU0VdjQIxv1QP8APovRmX2PhUDOfMV3ORpXUGD1kC9H1Gi1SArJs2UgzPaCwDBRxFRzQ04UmLQGD+CVgUKIqSpBBKLOs2y3X3TJ0s8TwwQYeDyj8RLktqwYSBI+oyM8EJPHCLKSlbCUQVoogYmWbABAEAB+QpVyqpKatSnqDyPE9GqayiCC0TAeGURAxtvfYFyXF1NWqlK7OZP5oCYXKAIKg4XywIF0QsJqLzACCetAQpPE69b7ppXrsNIwzBtCYawF1S7l2ulM0xoSYkH5CVCmbfhW0XZcXIATTTSgMcQTBuPyUG7rdZaIqRhhidJ5Bycp1h6usQ8XB2gpfv2w7joYU66IYpGUZp7rnUe56DVerlnxkr6MWBv6HSdBwyERgnRXOVN0xa9hsd+xnmewMnMe4yH5n6mHQBCIIgA
Related Issues:
Couldn't find anything directly related

@nmain
Copy link

nmain commented Nov 22, 2019

Duplicate of #13962

@oatkiller
Copy link

This shows that the binding can be narrowed despite not being a union, as long as its narrowed with a switch statement: http://www.typescriptlang.org/play/?ssl=1&ssc=1&pln=27&pc=2#code/C4TwDgpgBAggxsAlgewHZQLxQN4CgpSiQBcUAhqiLgL665HTxJoyY76HgSkDkZPHMGRAAbZGQAmpABQBKTAD4oAN2SIJNOg1gIUqAEJs8BBrwBGAgkNHipUAM7AATolQBzTfS6wRIgPIArk5MevZsISxQAD46zAZ0EhBwImRO0ABmAai6aFCI9hGofqgQfumBTvbSZDmopIWypDVxeWEwvhWF9glJKWlQcGiODsgAthAA4ojKEKiF9bV0APRLUACS6YQAFvmthE4BEAA0UADu0ADWqMin29DNento0MibyEHdiJvS+YXFpeUPtJ7GNJtNZg15MYoCsBhQoGZoKhUk4bhAJAiQA5TohgHAthx7Di8VsoMDQVMZnNagA6BhQjgEOBkezQPg8Ugg8aUiG06xiSRyDi0ahAA

type Action = {
  type: any
}

type ActionA = {
  type: 'a'
  payload: () => void
}

type ActionB = {
  type: 'b'
  payload: string
}

type AllOurActions = ActionA | ActionB

declare function isActionOneOfOurs(action: Action): action is AllOurActions

declare const someGivenAction: Action

// If this is true, we know the action is one of ours
if (isActionOneOfOurs(someGivenAction)) {
  // can be narrowed by switch
  switch (someGivenAction.type) {
    case 'a': someGivenAction.payload()
  }
}

@RyanCavanaugh
Copy link
Member

Duplicate #30557

@RyanCavanaugh RyanCavanaugh added the Duplicate An existing issue was already created label Nov 22, 2019
@fatcerberus
Copy link

fatcerberus commented Nov 23, 2019

Narrowing by discriminant only applies when the declared type of the identifier is a union type

What makes the switch statement different that narrowing works for that but not for if? I couldn't find anything in the linked issues that explained this discrepancy.

@typescript-bot
Copy link
Collaborator

This issue has been marked as a 'Duplicate' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

6 participants