Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Type-inference for switch-case block with dynamic case strings

A tricky TS challenge.

Say one has two objects with a type property, and a specific property, fooData or foo2Data, that is specific to the type value of the object.

For example:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

const dynamicString: string = 'abc'

const fooTypeName = `foo${dynamicString}` as const // const fooTypeName: `foo${string}`
const foo2TypeName = `foo${dynamicString}2` as const // const foo2TypeName: `foo${string}2`

type FooObj = {
  type: typeof fooTypeName
  fooData: string
}

type Foo2Obj = {
  type: typeof foo2TypeName
  foo2Data: string
}

Say one unionizes the two object types, to create an "all possible objects" type, AllObjs:

type AllObjs = FooObj | Foo2Obj

Now, say one wants to create a function that extracts the "data" value of an object typed as AllObjs. The sensible approach would be a switch-case block, as follows:

const extractData = (obj: AllObjs) => {
  switch (obj.type) {
    case fooTypeName:
      return obj.fooData
    case foo2TypeName:
      return obj.foo2Data
    default:
      return null
  }
}

This currently errors. Typescript is not able to do type inference in the switch due to the specific chosen values for fooTypeName and foo2TypeName. If one appends any character other than 2 to fooTypeName, or prepends it with any character, it works. What is typescript doing here, and why?

I assume that it is something to do with set theory, and how foo2TypeName could potentially contain fooTypeName. However careful inspection of the code shows this to be impossible, because of the shared dependence on dynamicString (i.e. it is impossible to define dynamicString in such a way such that fooTypeName and foo2TypeName are the same).

Is Typescript just limited in this way?

Edit

As @jcalz and @catgirlkelly explain, even though it is impossible for fooTypeName and foo2TypeName to be the same (no matter what value of dynamicString), because each reference to dynamicString is treated as anything, foo${string}2 contains foo${string}, it is a compiler limitation that it does not treat the two strings as different, hence preventing type inference in the switch-case block.

Playground link

>Solution :

const dynamicString: string = 'abc'

// const fooTypeName = `foo${dynamicString}1` as const
const fooTypeName = `foo${dynamicString}` as const
const foo2TypeName = `foo${dynamicString}2` as const

fooTypeName‘s type is

`foo${string}`

foo2TypeName‘s type is

`foo${string}2`

fooTypeName is wider than foo2Typename, so TypeScript simplifies the union of the two to just foo${string}, disallowing you to discriminate the two constituents of AllObjs.

But why is it wider?

Consider the following case:

fooTypeName = "fooAnythingGoes2" // âś… foo, followed by any string
foo2TypeName = "fooAnythingGoes2" // âś… foo, followed by any string and then 2

Clearly, both assignments are valid, and they are the same. Now if we add any other character to the end of fooTypeName:

const fooTypeName = `foo${dynamicString}.` as const // foo${string}.

They’re different:

fooTypeName = "fooAnythingGoes2" // đźš« foo, followed by any string and then .
fooTypeName = "fooAnythingGoes." // âś… foo, followed by any string and then .
foo2TypeName = "fooAnythingGoes2" // âś… foo, followed by any string and then 2

More cases which might help you understand listed here.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading