Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Nested Tagged Unions

See original GitHub issue

TypeScript Version: 2.5.2


type A = { type: "a", a: number }
type B = { type: "b", b: number }

type X = { type: A, a: string }
type Y = { type: B, b: string }

let x: X | Y

if (x.type.type === "a") {
	x.a // Type Error

Expected behavior:

I would expect x to have type X inside the if-statement after disambiguating the nested tagged union types.

Actual behavior:

Issue Analytics

  • State:open
  • Created 6 years ago
  • Reactions:76
  • Comments:23 (6 by maintainers)

github_iconTop GitHub Comments

pzavolinskycommented, Jul 1, 2022

👋 currently there is a way (with some heavy type foo) to actually get this to work, I’ll use @rakeshpai example, but others should work as well:

// The nested tagged union type
type X =
  | { type: { name: 'foo' }; settings: { a: boolean; b: string } }
  | { type: { name: 'bar' }; settings: { c: string[] } }
  | { type: { name: 'baz' }; settings: { d: number[] } };

// ========================================================================== //

// The type-level function that, given a name, returns the type(s) within the union for that name.
// The second argument is required to get "distributive conditional types" to work (more on this after
// this code snippet)
type GetTypeForName<TName, TX = X> = TX extends { type: { name: TName } }
  ? TX
  : never;

// Type predicate to run the narrowing in the outer object based on a nested discriminant
const is = <TName extends X['type']['name']>(
  x: X,
  name: TName,
): x is GetTypeForName<TName> => === name;

// ========================================================================== //

// The function you actually wanted to write, all of the above is write-once boilerplate
const test = (x: X) => {
  // What you would like out of the box:
  // if ( === 'baz') console.log(x.settings.d);

  // What you can get, for now:
  if (is(x, 'baz')) console.log(x.settings.d);

You can read more about distributive conditional types here.

Also, I covered some of the building blocks for this solution ☝️ in this fan fiction story about typescript metaprogramming.

Hope this helps!

tejasmanoharcommented, Nov 1, 2019

+1 here. I debugged this for a bit to realize that unions can only be discriminated at the top level / direct. I’m also copying fields down the chain for this purpose

Read more comments on GitHub >

github_iconTop Results From Across the Web

Typescript Nested Tagged/Discriminated Union Types
If I try to model it using Typescript's discriminated unions, I'll face the problem that LoginReply needs to have a property kind with...
Read more >
Nested Union Types - Learn - Elm Discourse
I have multiple union types that in turn belong to another union type like so: type Foo = OneFoo | TwoFoo | ThreeFoo...
Read more >
Union declaration -
Optionally prepended by nested-name-specifier (sequence of names and ... Union-like classes can be used to implement tagged unions.
Read more >
Tagged Unions and ReScript Variants - DEV Community ‍ ‍
In TypeScript, we'd use a so called Discriminated Union Type (Tagged Unions) to be able to encode the conditional object shape within the ......
Read more >
Zig Language Reference
Tagged union ; extern union; packed union; Anonymous Union Literals ... The struct is nested (declared) inside of a union. try expect(true); }...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Post

No results found

github_iconTop Related Hashnode Post

No results found