question-mark
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.

Type alias name (union) is not preserved when using index access/lookup types

See original GitHub issue

TypeScript Version: 3.5.2

Search Terms: indexed access lookup types type alias naming

Code

Given:

type MyUnion = 1 | 2 | 3;

// Hover this
type MyObject = { value: MyUnion };

If I hover over MyObject, I see { value: MyUnion; }.

However, given:

type MyRecord = {
    Foo: 1;
    Bar: 2;
    Baz: 3;
}

type MyUnion = MyRecord[keyof MyRecord];

// Hover this
type MyObject = { value: MyUnion };

If I hover over MyObject, I see { value: 1 | 2 | 3; }.

I would expect the same behaviour as we see in my first example ({ value: MyUnion; }). The name of the type alias, MyUnion, should be preserved and its usage/reference inside of MyObject should be shown.

To give some context on my real world use case for this: I am using Unionize to create tagged unions (to avoid boilerplate for constructors, matchers, and type predicates). However, because of the issue described above, the types become very difficult to inspect/read: https://github.com/pelotom/unionize/issues/60.

Under the hood, Unionize does something like this:

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

type MyUnionRecord = {
    Foo: Foo;
    Bar: Bar;
    Baz: Baz;
};

type MyUnionTaggedRecord = { [K in keyof MyUnionRecord]: { tag: K; value: MyUnionRecord[K] } };
type MyUnion = MyUnionTaggedRecord[keyof MyUnionTaggedRecord];

// Hover this
type MyObject = { value: MyUnion };

If we hover over MyObject here, we see:

{
    value: {
        tag: "Foo";
        value: Foo;
    } | {
        tag: "Bar";
        value: Bar;
    } | {
        tag: "Baz";
        value: Baz;
    };
}

… when we really want to see { value: MyUnion }.

As I demonstrated in https://github.com/pelotom/unionize/issues/60, this scales really badly, e.g. when nesting unions.

Issue Analytics

  • State:open
  • Created 4 years ago
  • Reactions:3
  • Comments:6 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
whatisaphonecommented, Sep 18, 2019

I’ve never dug into the compiler source, but I have an idea: Keep one type object around for typechecking, and another separate object for diagnostic reporting (only if different than the first).

So for Ryan’s example, the AST for q and v might look something like this:

{
  name: "q",
  type: Union([String, Number, Boolean]),
  prettyType: Union([
    TypeAlias("SN", Union([String, Number]),
    Boolean,
  ]),
}
{
  name: "v",
  type: Union([String, Number, Boolean]), // reference-equal to the previous type
  prettyType: Union([
    String,
    TypeAlias("NB", Union([Number, Boolean]),
  ]),
}

type is still the literally the same object, but prettyType is different and would be used when reporting errors. This would probably increase memory footprint a bit, but shouldn’t noticeably increase typechecking time. For any declaration where type aliases aren’t involved or the distinction isn’t helpful, prettyType can be set to null, and reporting would fall back to the actual type. This would hopefully keep memory issues in check.

Of course I’m just this random guy and the devil is always in the details.

1reaction
RyanCavanaughcommented, Jul 8, 2019

The fact that any type alias at all shows up is due to trickery and the limitations of that trickery are apparent in these examples.

The trick is that if the very first time we make a type it’s because it’s initializing a type alias, then future displays of that type use the alias name. This is a desirable heuristic because you don’t want some rando .d.ts file to say

type AcrobaticBackCompatKey = string | number | boolean;

and then have your function

function fn(stringish: string | number | boolean) { }

display back as

fn(stringish: AcrobaticBackCompatKey)

which you didn’t write.

In this example, it seems the type 1 | 2 | 3 got created before it got assigned to the type alias, preventing it from being the “given name” for 1 | 2 | 3.

“Why not just make type aliases be a named pointer to the underlying type?”

Nothing’s impossible, but this has some performance impacts that aren’t immediately obvious. For example

type SN = string | number;
type NB = number | boolean;

declare let q: SN | boolean;
declare let v: string | NB;
q = v;
v = q;

In this example, the assignments can be detected to be correct almost “for free” because their types are literally the same object - there’s no way to tell q and v apart. If we have to keep type aliases around all the time, then this operation becomes a lot more expensive.

So it’s a trade-off and we’d have to figure out ways to make it not as expensive as it might be, or someone can maybe propose tweaks to our trickery that would make things more palatable while still not messing up existing type display too badly.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Alias names in union / union all query - SQL Server
The alias part works just for the first one because there is no way for the database to identify which column would be...
Read more >
Use a union query to combine multiple queries into a single ...
The last part of this SQL statement determines the ordering of the combined records by using an ORDER BY statement. In this example,...
Read more >
Structured binding declaration (since C++17)
Binds the specified names to subobjects or elements of the initializer. Like a reference, a structured binding is an alias to an existing...
Read more >
SQL UNION overview, usage and examples
List the non-common rows from the first set. Note: It is very easy to visualize a set operator using a Venn diagram, where...
Read more >
[dcl.dcl]
If the decl-specifier-seq contains no typedef specifier, the declaration is called a function declaration if the type associated with the name is a...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

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 Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found