#Types not narrowing with discriminated union

23 messages · Page 1 of 1 (latest)

hearty helm
#

It seems like this simple type narrowing doesn’t work as it should:

tepid plazaBOT
#
.selrond#0

Preview:```ts
const address = {
id: '123',
fieldConfig: {
type: 'address'
},
value: {
city: 'asdf'
}
}

const num = {
id: '3456',
fieldConfig: {
type: 'number'
},
value: 123
}

type Field = typeof address | typeof num

const fn = (field: Field) {
...```

floral bramble
#

you have a few problems here

#

first of all, if you look at the types of the fieldConfig properties you'll see they are merely { type: string }. there's no type-level difference between address.fieldConfig and num.fieldConfig

#

you might've meant to use as const on those values

#

however, even if you do that your narrowing attempt will not work

#

discriminated unions must follow a specific pattern of having a literal-typed key at the top level

#

the only extra narrowing rule you get with discriminated unions is that x may be narrowed when you check x.field, if field is a discriminant property

#

when you check field.fieldConfig.type, only field.fieldConfig will ever be narrowed, not field itself. it's not "deep"

#

if you can move type up to the top level it'll work

hearty helm
#

Wow, thanks!
Is this behavior documented somewhere? There are lots of examples on TS docs, but nothing explicit about the mechanism ike you’ve described

tepid plazaBOT
#

@hearty helm Here's a shortened URL of your playground link! You can remove the full link from your message.

.selrond#0

Preview:```ts
const address = {
id: '123',
fieldConfig: {
type: 'address',
address: 'true'
},
value: {
city: 'asdf'
}
}

const num = {
id: '3456',
fieldConfig: {
type: 'number',
num: true
},
value: 123
}

type Field = typeof address | typeof num
...```

floral bramble
#

the documentation page i linked to above says this:

When every type in a union contains a common property with literal types, TypeScript considers that to be a discriminated union, and can narrow out the members of the union.

In this case, kind was that common property (which is what’s considered a discriminant property of Shape). Checking whether the kind property was "circle" got rid of every type in Shape that didn’t have a kind property with the type "circle". That narrowed shape down to the type Circle.

hearty helm
floral bramble
#

right, it says what does work (and that's all that works). i do agree it could spell this out more clearly

floral bramble
hearty helm
floral bramble
#

ah yeah i don't think "contains" implies recursion or anything there. maybe it's clearer when i swap it for "has":

When every type in a union has a common property with literal types

hearty helm
floral bramble
#

i believe it's simple for performance reasons. computing every possible permutation at every depth of an object would be expensive/slow. i'll see if i can dig up any source for that