The type of B is displayed as A when `type B = A` is used. Why is it displayed as `any` when `type B = A | A` is used instead?

56 views Asked by At
import { A } from "https://example.com/type.d.ts";
type B = A | A

Playground

If you hover on B in both TS Playground and VS Code, it will display it as A in the case type B = A and any in the case type B = A | A. Regardless of whether A is known or not, I expect they should be consistent. Why is that not the case?

1

There are 1 answers

0
jcalz On BEST ANSWER

According to microsoft/TypeScript#54630, specifically this comment by the TS team dev lead:

There are no specific guarantees on what happens when there is an error type in your program.

When you import something that can't be found, you get a compiler error:

import { A } from "https://example.com/type.d.ts"; // error!
// -------------> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Cannot find module 'https://example.com/type.d.ts' or its corresponding type declarations.

and any references to it are going to be shown as whatever TypeScript wants to show it as. It will often tend to show up as the any type, because

The hover shows "any" for lack of a better thing to display. It could be differentiated, but these things tend to not stick around in programs very long.

That explains why you see any sometimes.


As for why it shows A instead of any when you change from

type B = A | A
//   ^? type B = any

to

type C = A;
//   ^? type C = A

there's no documented and stable rule by which such types are displayed. Assuming that A is sometimes displayed as any, then it's really the compiler's prerogative to decide which one of those it wants to display. Even if A weren't an error, you'd still have such a dichotomy:

type D = { a: string };

type E = D;
//   ^? type E = { a: string; }

type F = D[];
//   ^? type F = D[];

There you have type D, which is clearly an alias for {a: string}. Some uses of D get reduced to {a: string}, while others stay as D.

These differences are only how the type gets displayed to the user, and shouldn't affect the actual identities of the types. There are no supported official mechanism by which a developer can just tell the compiler which form she would prefer to see when displaying a type. There are various open feature requests like microsoft/TypeScript#28508, microsoft/TypeScript#31940, and microsoft/TypeScript#45954, which could possibly change that if they were implemented, but for now it's not part of the language. The compiler uses heuristics to determine which is the type to display; these heuristics perform well enough in a wide range of use cases, but it is necessarily the case that they cannot meet everyone's needs simultaneously. For every person who wants D to be displayed as {a: string}, there is an equal and opposite person who wants it do be displayed as D, and they both have very important reasons for it to be that way, and both think their way would make things "consistent". More developer control would be nice, but without that, you just get what you get.

So: I can't easily say for certain why you see any for A | A but A for A, without stepping through the TypeScript compiler code and maybe finding pull requests that document the parts responsible. But even if I did that, it wouldn't be particularly enlightening, and it could easily change in a future version of TypeScript. As long as two types are identical, in some sense it's completely up to the compiler to decide which one to show and when.

Playground link to code