TypeScript: Tooltips / IntelliSense: Don't resolve type aliases (aka "semantic sugar") set explicitly

We have defined a bunch of “semantic sugar” type aliases like: image

Status quo (bad): VS Code is resolving these along the alias chain in tooltips and IntelliSense suggestions (which kinda makes non-sense of the type alias in the first place) image(c.f. hovered tooltip over this.url at bottom)

Better: Let VS Code show UrlString as type here instead of string to aid developers with semantic sugar as of what type of string to expect.

Alternative: Show both, i.e. BufferingWebSocket.url: UrlString (= string).

About this issue

  • Original URL
  • State: open
  • Created 5 years ago
  • Reactions: 93
  • Comments: 29 (2 by maintainers)

Most upvoted comments

@miyauchiakira True, just adding & {} (which does not change the type at all) causes TypeScript not to expand the type. I have no idea why this workaround works. Ugly, but effective – still would prefer TypeScript to “just work”.

+1 for this.

In our project, I wanted to declare some semantic types representing numbers, to remove some of the ambiguity of “what does this random float value actually mean?”, so I added these type alias declarations:

type percentage = number
type timeInSeconds = number
type timeInMilliseconds = number
type radians = number
type degrees = number
type gameUnits = number

I was quite disappointed to see that the assorted tooltips/intellisense did not keep the alias in tact, and just resolved down to a number.

Faced same issue when wanted to short Record<string, unknown> to Dict via type Dict = Record<string, unknown>, as there was alot of them, and saw this alias nowhere! For example following function signature:

function foo<
  T1 extends Dict,
  T2 extends Dict,
  T3 extends Dict,
  T4 extends Dict
>(
  param1: T1,
  param2: T2,
  param3: T3
): T4

Showed in tooltip as:

function foo<T1 extends Record<string, unknown>, T2 extends Record<string, unknown>, T3 extends Record<string, unknown>, T4 extends Record<string, unknown>>(param1: T1, param2: T2, param3: T3):  T4

I was very frustrated when faced this problem! Please fix this or at least add setting to change this behavior! Definitions are just unreadable!

+1

It gets even worse with generic functions. For rich type modelling, type hints like this:

(methode) toResult<Readonly<{
    searchIndexId: string;
    searchResult: Readonly<{
        pageInfo: Readonly<{
            pageIndex: number;
            totalNumPages: number;
            numItemsOnThisPage: number;
            numItemsPerPage: number;
            totalNumItems: number;
        }>;
        pageData: readonly Readonly<...>[];
    }>;
}>>(response: ServerResponse<...>): Result<...>

are almost useless. I can’t even see the function parameter’s full type, because all the space is used up for the generic type description. What the above should have said, is:

(methode) toResult<SearchForProjects>(response: ServerResponse<SearchForProjects>): Result<string, SearchForProjects>

honoring my type aliases.

It is NOT a new type of course, it’s just an alias - I mean: That’s the whole point of an alias.

Nonetheless nothing contradicts showing the alias in tooltips (or both - c.f. Alternative in my OP) IMHO.

Aliasing doesn’t actually create a new type - it creates a new name to refer to that type. Type aliases don’t create a new name — for instance, error messages won’t use the alias name.

These two sentences are contradicting another, aren’t they? –> it doesn’t CREATE TYPE - it CREATES NAME –> it doesn’t CREATE NAME ???

So issue is 4 years old, however still no solution from the TS team…

Anyways, yet another TypeScript hack to have it working as expected. Since & {} doesn’t always work, e.g.:

type HexColorCode = string & {} // Works fine
type UUID = `${string}-${string}-${string}-${string}` & {} // Will show `${string}-${string}-${string}-${string}` instead of UUID
type DateTime = Date & {} // Will show Date instead of DateTime

, what I generally do is include some method from the original type in {}:

type HexColorCode = string & {}
type UUID = `${string}-${string}-${string}-${string}` & { toString: string['toString'] }
type DateTime = Date & { toString: Date['toString'] };

This fixes IntelliSense problem and doesn’t clutter up the code with unnecessary stuff (like { _: never }).

+1 for this.

Maybe this would work: by default, hovering over a type alias should simply show it’s name, however while also holding CTRL key (or anything), it would expand the alias to what it actually is…?

type alias<t> = t & { _:never }
type my_type = alias<number | { "any": "complicate type" }>

worked for me

+100 for this!

Just wondering if there’s been any progress?

In my mind there’s no obvious drawback for this. When working with complex types, it quickly makes the tooltip obscure and hard to understand the error. At the very least, an option to show alias instead of resolving should be enough. What prompted me to write this comment was the need I had to temporarily replace the aliased type with a simpler version just so I could understand the tooltip. I spent an absurd amount of time trying to understand the type resolution and not understanding why the piece of functionality broke when all it was trying to tell me was that I’d forgot to type a particular field as optional. This was completely inline with the changes I was doing but the unhelpful tooltip sent me down a rabbit hole. As soon as I changed the aliased type to something simpler I was able to pick up the problem immediately.

Just to extra emphasise this, consider the following, in React:

type MyElem = ReturnType<React.FC<SomeProps>>
type ElemSet = MyElem | OtherElem | AnotherOne

This quickly gets incomprehensible if MyElem is resolved, even more so if the other elem types are also resolved.

@plaa @miyauchiakira By the way, strictly speaking, the proper term for that technique is “branded types” (or “type branding”). It is a workaround to mimic nominal typing in structural type system, but not “nominal types” itself.

I just started using yup for input validation. An awesome aspect of it is that it produces the types out of the validation schema, so they’re guaranteed to match. But Intellisense and error messages becomes completely useless when you get this:

Example playground

intellisense-sucks

I second this suggestion as well. If I create an alias, it’s specifically to see the alias rather than seeing the true type which is often more complex. An option to resolve the alias on demand inside the tooltip would be nice but not necessary.

I just stumbled across this behavior as well and would like to see the recommended change. Having type aliases can make code clearer and easier to read. But when this information gets erased in the tooltip, it looses its purpose.

type alias<t> = t & { _?: never } // instead { _: never}
type my_type = alias<number | { "any": "complicate type" }>

Worked for me

If you don’t see images click here | Visual explanation of how its work

export type PairId = Readonly<`${AssetSymbol}_${AssetSymbol}`>

Untitled.png

type alias<t> = t & { _?: never }
export type PairId = alias<Readonly<`${AssetSymbol}_${AssetSymbol}`>>

Untitled.png

Like the above, I sometimes also like to see the type with aliases resolved.

But not all the time. Perhaps it could be when you hold down a key before mousing over something, it resolves all the aliases, otherwise it keeps them. Like shift maybe.

I honestly thought i had made a mistake when the alias type did not show up in intellisense. It really doesn’t make sense to me when you set the alias explicitly like this.

I hugely appreciate this feature (or option). Showing internal structure instead of type alias name has never made any sense to me when I want to see the type of an identifier in a tooltip. This suggestion definitely helps the “Documentation as Code” idea.