Description
Search Terms
Force TS to always alias type, optional property, never expand type
Suggestion
It would be nice to have a way to annotate a type alias and tell TS, "As much as possible, do not expand this type alias in emit". I don't know what syntax it should have. But maybe,
type interface PleaseDoNotExpand<T> = /*complex type*/;
In all aspects, the above is a type alias, except for the fact that its type will not expand in emit (as much as possible).
class
and interface
types have this behaviour. Their identifiers are used as much as possible in emit.
The "readability" of a particular emit is almost always subjective. So, giving developers some control over the emit can make code easier to understand.
Here's an example, where using a type alias in a union causes the type alias' identifier to not be used in emit,
type PleaseDoNotExpand<T> =
| T
| { x : T }
;
/*
Expected: const a: PleaseDoNotExpand<number>
Actual : const a: PleaseDoNotExpand<number>
*/
declare const a: PleaseDoNotExpand<number>;
/*
Expected: const b: PleaseDoNotExpand<number>|undefined
Actual : const b: number | {x: number;} | undefined
*/
declare const b: PleaseDoNotExpand<number>|undefined;
If PleaseDoNotExpand<>
were a class
or interface
, we would have const b: PleaseDoNotExpand<number>|undefined
Use Cases
What do you want to use this for?
- Better emit for
.d.ts
files - Better emit for tooltip hover
- Better emit for error messages
Some type aliases are hundreds of lines long, after expansion. These type aliases usually have short, intuitive identifiers. But those identifiers tend to get lost when used in union types (and optional properties). See #35616 for more examples.
What shortcomings exist with current approaches?
There is no "general purpose" workaround for the current problem.
So far, I've thought of two workarounds. But they only work for very specific use cases.
Workaround 1: The type alias has statically known members
The idea is to use an interface
to extend the type alias. From that point, only the interface
's identifier is used in emit. This has the most desirable behaviour. If it could be extended to work for all use cases, then I wouldn't have this feature request.
Workdaround 2: The type alias is being removed by unions/optional properties
The idea is to create a new type alias that is a union of the original type and the new union elements. However, it does not always work and I don't know why. But this is better than always expanding.
Examples
//No idea about syntax
type interface PleaseDoNotExpand<T> =
| T
| { x : T }
;
/*
const a: PleaseDoNotExpand<number>
*/
declare const a: PleaseDoNotExpand<number>;
/*
const b: PleaseDoNotExpand<number>|undefined
*/
declare const b: PleaseDoNotExpand<number>|undefined;
/*
//It makes sense to lose the identifier at this point
const c: number
*/
declare const c: Extract<PleaseDoNotExpand<number>, number>;
/*
//It makes sense to lose the identifier at this point
const c: { x : number }
*/
declare const d: Exclude<PleaseDoNotExpand<number>, number>;
Checklist
My suggestion meets these guidelines:
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
- This feature would agree with the rest of TypeScript's Design Goals.
Related
#34556 asks to never alias a type. This asks to always alias a type.