#Confusion around union types

10 messages · Page 1 of 1 (latest)

tight thorn
#

Hi all! I know TS pretty well (or so I thought) but am seeing some confusing behavior around type narrowing. Suppose I have these types:

type AAction = { type: 'a' }
type BAction = { type: 'b' }
type CAction = { type: 'c' }

type ARule = {
  action: AAction
  config: {
    key1: 'key1'
  }
}

type BRule = {
  action: BAction
  config: {
    key1: 'key1'
  }
}

type CRule = {
  action: CAction
  config: {
    key2: 'key2'
  }
}

type Rule = ARule | BRule | CRule

I can't tell why in the following code, TS is not able to see that r.config should only be { key1: 'key1' }:

let r: Rule

switch(r.action.type) {
  case 'a':
   r.config // this is { key1: 'key1' } | {key2: 'key2' }
}

By switching on r.action.type and selecting the 'a' case, why can TS not see that config must only be that of ARule?

quasi solstice
#

@tight thorn Typescript doesn't narrow through multiple layers of an object: checking r.action.type will narrow r.action but won't affect the type of r.

tight thorn
#

ahhh okay...hmm. Well, that's a pretty concise answer and nothing I can do about it 🙂 thank you!

#

I guess I could do something like

const isARule = <T extends Rule>(candidate: T): candidate is ARule => candidate.action.type === 'a'
quasi solstice
#

Yeah, if you can flatten out your rule definitions, that'd work a bit better, but if you can't you'd probably want to write custom type-guards.

tight thorn
#

and use if/else's instead of a switch

#

well, that was quick. Thank you again!

#

!resolved

quasi solstice
#

@tight thorn Here's a version you can use that should work for all the variants:

function isRuleType<T extends Rule["action"]["type"]>(rule: Rule, type: T): rule is Extract<Rule, { action: { type: T} }> {
  return rule.action.type === type;
}
tight thorn
#

oh nice...I like that