最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

typescript - Discriminated union type with discriminating property as union - Stack Overflow

programmeradmin0浏览0评论

I have a discriminated union (do I?) with discriminating property x of types number | undefined and number. I thought checking for x === undefined would suffice to narrow type down, but it doesn't work (typescript 5.7.3). Surprisingly it works if instead of number I use any literal type or even literal type union.

const assertIsOption1 = (option1: 'option1') => undefined;

type CreateUnionType<T> = (
    { x: T | undefined, y: 'option1'} |
    { x: T, y: 'option2' }
)

// Doesn't work with "broader types":

type UnionWithBroadTypeInDiscriminatoryKey = CreateUnionType<number>

const obj = { x: undefined, y: 'option1' } as UnionWithBroadTypeInDiscriminatoryKey;


if (obj.x === undefined) {
    assertIsOption1(obj.y)
}

// does work with literals:

type UnionWithLiteralTypeInDiscriminatoryKey = CreateUnionType<5>
const obj2 = { x: undefined, y: 'option1' } as UnionWithLiteralTypeInDiscriminatoryKey;

if (obj2.x === undefined) {
    assertIsOption1(obj2.y)
}

// and does work with union of literals:

type UnionWithLiteralTypeInDiscriminatoryKey2 = CreateUnionType<5 | 6 | 'some string literal'>
const obj3 = { x: undefined, y: 'option1' } as UnionWithLiteralTypeInDiscriminatoryKey2;

if (obj3.x === undefined) {
    assertIsOption1(obj3.y)
}

Click here to view and run this code on TypeScript Playground

I have a discriminated union (do I?) with discriminating property x of types number | undefined and number. I thought checking for x === undefined would suffice to narrow type down, but it doesn't work (typescript 5.7.3). Surprisingly it works if instead of number I use any literal type or even literal type union.

const assertIsOption1 = (option1: 'option1') => undefined;

type CreateUnionType<T> = (
    { x: T | undefined, y: 'option1'} |
    { x: T, y: 'option2' }
)

// Doesn't work with "broader types":

type UnionWithBroadTypeInDiscriminatoryKey = CreateUnionType<number>

const obj = { x: undefined, y: 'option1' } as UnionWithBroadTypeInDiscriminatoryKey;


if (obj.x === undefined) {
    assertIsOption1(obj.y)
}

// does work with literals:

type UnionWithLiteralTypeInDiscriminatoryKey = CreateUnionType<5>
const obj2 = { x: undefined, y: 'option1' } as UnionWithLiteralTypeInDiscriminatoryKey;

if (obj2.x === undefined) {
    assertIsOption1(obj2.y)
}

// and does work with union of literals:

type UnionWithLiteralTypeInDiscriminatoryKey2 = CreateUnionType<5 | 6 | 'some string literal'>
const obj3 = { x: undefined, y: 'option1' } as UnionWithLiteralTypeInDiscriminatoryKey2;

if (obj3.x === undefined) {
    assertIsOption1(obj3.y)
}

Click here to view and run this code on TypeScript Playground

Share Improve this question edited Jan 21 at 11:43 Rafael 2,0492 gold badges26 silver badges63 bronze badges asked Jan 21 at 11:11 yjayyjay 1,0235 silver badges14 bronze badges 3
  • It's a strange behavior, if instead of const obj = { x: undefined, y: 'option1' } as UnionWithBroadTypeInDiscriminatoryKey; you write const obj: UnionWithBroadTypeInDiscriminatoryKey = { x: undefined, y: 'option1' }; it works as expected, but its strange, in both cases the type of obj is UnionWithBroadTypeInDiscriminatoryKey, so AFAIK in both cases should work exactly the same – Daniel Cruz Commented Jan 21 at 13:21
  • 1 It's not a discriminated union with number in there; discriminants need to be literal types. Neither number nor number | undefined is a literal type. If you want to refactor to this version then undefined is a literal type and can be used to discriminate. Does that fully address your question? If so I'll write an answer or find a duplicate. If not, what's missing? – jcalz Commented Jan 21 at 14:17
  • Thank you! I think that's it! Would you be able to link some docs? I was convinced I've seen discriminated unions with "typeof x === 'y'" condition narrowing the type, but apparently that also doesn't work. Are you aware of any propositions / plans to improve how unions work? – yjay Commented Jan 21 at 14:58
Add a comment  | 

1 Answer 1

Reset to default 2

Discriminated unions need to have a valid discriminant property that is a unit/literal type of a union of such types. The type undefined counts as a discriminant by itself, but neither the wide type number nor number | undefined counts, so you can't check your type's x property to discriminate the union. (Note that your type is a discriminated union with y being a valid discriminant, but for some reason you aren't trying to check that property.) This restriction could be considered a design limitation, as described at microsoft/TypeScript#30506; allowing wider discriminants is not part of the language, and judging from the issues linked within that issue, it doesn't look like it will become one soon (there is an implementation at microsoft/TypSeScript#60718 but apparently it breaks lots of real world code).

There is some support for non-literal discriminants where some members of the union have wider type, but I believe at least one member of the union needs to be a "true" discriminant property. So if one union member has undefined as the discriminant, then other members can have wide types like number. This leads to the following refactoring of your types:

type CreateUnionType<T> = (
    { x: undefined, y: 'option1' } |
    { x: T, y: 'option1' | 'option2' }
)

which works as intended:

const obj = { x: undefined, y: 'option1' } as UnionWithBroadTypeInDiscriminatoryKey;
if (obj.x === undefined) {
    assertIsOption1(obj.y); // okay
}

With the example code it does seem like you probably want to keep your original type and discriminate based on y to start with, but I guess that's out of scope for the question as asked.

Playground link to code

发布评论

评论列表(0)

  1. 暂无评论