My issue is how types are displayed with IntelliSense, I need IntelliSense to give priority to the alias type instead of the union of strings.
In this example the type of el
should be T
, but instead it gives "a" | "b"
//this does not work:
const o = {a:"a", b:"b"};
type T = keyof typeof o;
const v: T[] = ["a"];
const el = v[0];
//this works:
type T2 = "a" | "b";
const v2: T2[] = ["a"];
const el2 = v2[0];
Is there any way to fix this? Thanks.
CodePudding user response:
The types T
and "a" | "b"
are equivalent, and it is essentially up to the discretion of the compiler which form to display in IntelliSense. It could choose T
, or "a" | "b"
, or keyof typeof o
, or "b" | "a" | "a" | "b" | "a" | "a"
or Exclude<"c" | "r" | "a" | "b", "c" | "r">
or anything else as long as it's equivalent. The compiler uses heuristics to make its decisions, imperfectly predicting which forms are most likely to be what developers want to see. These predictions are imperfect both because the compiler is not as intelligent as the developers it's trying to please, and because different developers will disagree about which forms are desirable. Someone might come along and prefer "a" | "b"
to T
in your code example.
Anyway, every once in a while the IntelliSense type display heuristics are adjusted in an attempt to make people happier. For example, see the support added for "smarter" type alias preservation in TypeScript 4.2, along with its implementation at microsoft/TypeScript#42149 and microsoft/TypeScript#42284.
It looks like when you add some indirection in the form of a union or intersection, the heuristics tend to favor an alias name instead of expanding out the type definition fully. Finding a union or intersection that doesn't change the type but also doesn't get immediately reduced to the original representation is a little tricky, you can't write keyof typeof o | never
or keyof typeof o & never
, because those get aggressively reduced to keyof typeof o
which displays as "a" | "b"
and not T
.
One way that currently seems to work (as of TS4.8) is to intersect with {}
, the empty object type (which also reduces, but not immediately):
const o = {a:"a", b:"b"};
type T = (keyof typeof o & {});
const v: T[] = ["a"];
const el = v[0];
// const el: T // <-- displays as T and not "a" | "b"
I can't guarantee that this will always work, since again, this is up to the discretion of the compiler and the possibly-changing heuristics in future versions of the language. (This caveat applies a lot of TypeScript code, since the whole language does change fairly rapidly.)
But for now it behaves as you like.