Skip to content

UX issues when inspecting types (truncation, type aliases not used) #60

Description

@OliverJAsh

We're using Unionize pretty heavily at Unsplash (thank you!). I would like to share some UX feedback relating to how the union types appear when inspected. To do so, I will use an example.

Vanilla tagged union

First let's see how type inspection works for a sans-Unionize union:

type Foo = { foo: number };
type Bar = { bar: number };
type Baz = { baz: number };

type MyNestedUnion =
  | {
      tag: "NestedFoo";
      value: Foo;
    }
  | {
      tag: "NestedBar";
      value: Bar;
    }
  | {
      tag: "NestedBaz";
      value: Baz;
    };

// Hover this
type MyUnion =
  | {
      tag: "Foo";
      value: Foo;
    }
  | {
      tag: "Bar";
      value: Bar;
    }
  | {
      tag: "Baz";
      value: Baz;
    }
  | {
      tag: "Union";
      value: MyNestedUnion;
    };

// Hover this
declare const fn: (f: MyUnion) => void;

Hover over type MyUnion and you'll see:

type MyUnion =
  | {
      tag: "Foo";
      value: Foo;
    }
  | {
      tag: "Bar";
      value: Bar;
    }
  | {
      tag: "Baz";
      value: Baz;
    }
  | {
      tag: "Union";
      value: MyNestedUnion;
    };

Great! Hover over const fn and you'll see:

const fn: (f: MyUnion) => void;

Great! No problems here.

Unionize union

Now if we define the equivalent tagged union but using Unionize:

import { ofType, unionize, UnionOf } from "unionize";

type Foo = { foo: number };
type Bar = { bar: number };
type Baz = { baz: number };

const MyNestedUnion = unionize(
  {
    NestedFoo: ofType<Foo>(),
    NestedBar: ofType<Bar>(),
    NestedBaz: ofType<Baz>(),
  },
  { value: "value" },
);
type MyNestedUnion = UnionOf<typeof MyNestedUnion>;

const MyUnion = unionize(
  {
    Foo: ofType<Foo>(),
    Bar: ofType<Bar>(),
    Baz: ofType<Baz>(),
    Union: ofType<MyNestedUnion>(),
  },
  { value: "value" },
);
// Hover this
type MyUnion = UnionOf<typeof MyUnion>;

// Hover this
declare const fn: (f: MyUnion) => void;

Now hover over type MyUnion and you'll see:

type MyUnion = ({
    tag: "Foo";
} & {
    value: Foo;
}) | ({
    tag: "Bar";
} & {
    value: Bar;
}) | ({
    tag: "Baz";
} & {
    value: Baz;
}) | ({
    tag: "Union";
} & {
    value: ({
        tag: "NestedFoo";
    } & {
        value: Foo;
    }) | ({
        tag: "NestedBar";
    } & {
        ...;
    }) | ({
        ...;
    } & {
        ...;
    });
})

Observations:

  1. {
        tag: "Foo";
    } & {
        value: Foo;
    }
    … could be simplified to
    {
      tag: "Foo";
      value: Foo;
    }
    Is this something TS should do automatically for us? In the interim, is this something we can workaround, e.g. using Compact (which "cleans" types with lots of arithmetic)?
  2. In the sans-Unionize example, the MyNestedUnion type alias was shown nested inside of here. However in this Unionize example, the type alias MyNestedUnion is thrown away and the whole type structure is shown instead. Why? Is this a design limitation or bug in TS? Is it something we can workaround?
  3. Some types are truncated. This makes these types really difficult to work with. Often times, inspecting a type is not enough to understand how to work with it—instead you have to look deep into the definition. If we can't fix 2 (doing so would negate this), can we prevent truncation somehow?

Hover over const fn and you'll see:

const fn: (f: ({
    tag: "Foo";
} & {
    value: Foo;
}) | ({
    tag: "Bar";
} & {
    value: Bar;
}) | ({
    tag: "Baz";
} & {
    value: Baz;
}) | ({
    tag: "Union";
} & {
    value: ({
        tag: "NestedFoo";
    } & {
        value: Foo;
    }) | ({
        tag: "NestedBar";
    } & {
        ...;
    }) | ({
        ...;
    } & {
        ...;
    });
})) => void

In the sans-Unionize example, the MyUnion type alias was shown here. However in this Unionize example, the type alias MyUnion is thrown away and the whole type structure is shown instead. Why?

I appreciate there might not be much we can do from Unionize's side to help address these UX issues. However I imagine there's at least some discussions inside of TS we could chime into, so our voice is heard. 🤞

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions