-
Notifications
You must be signed in to change notification settings - Fork 12.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feature Request] Future-proof always-aliasing/never-expanding of mapped/intersection/union/etc. types #35654
Comments
We're a little more than a year into our adoption of TS at work, and at our end-of-year retrospective, TS was one of the topics of discussion. Everyone was generally quite happy with the transition, except one thing kept coming up, particularly from the juniors: reading and understanding the All of this is to say I think this is a great suggestion, but it may just be a band-aid on a deeper issue. This would certainly treat a symptom, but perhaps this suggestion works best if it's included as part of a larger overhaul in the diagnostic ergonomics of the language? One thing I thought about was taking an error message that contains an expanded type and showing its relevant alias in the erroneous frame, and then having a footnote that shows its expansion below the error, but I haven't had time to mock it up, let alone think it all the way through and open an issue/PR. Another idea I had was to have some kind of pattern matching in errors so I could specify error-time aliases, but I'm not sure what that would look like, so that's an even more raw idea. Point is, there are probably a ton of great ideas out there to improve error reporting, and if I'm not the only one who's noticed diagnostic messages could use some work, why not see what else is out there and come up with a long term solution? Or maybe that's just letting "great" be the enemy of "good." |
Not just a bandaid over error messages. But I agree. My main personal use case is,
Sometimes, you just want to look at the emitted .d.ts file and not the emitted .js or source .ts files. Also, regarding error messages, this issue is still very important. When TS expands types in emit, it causes TS to lose the "connection" to the original type alias. Downstream users of the library will just see the expanded type. Then, the code that generates error messages can't show the original type alias because that information no longer exists. As an (imaginary) example, //my-lib.ts
export type MyAlias<T> = //snip complicated stuff
export function myFunc<T> (t : T, myAlias : MyAlias<T>) : void {} After transpiling, //my-lib.d.ts
export type MyAlias<T> = //snip complicated stuff
export function myFunc<T> (t : T, myAlias : /* snip complicated stuff */) : void {} Then, when downstream users try to use //downstream user
import {myFunc} from "my-lib";
myFunc("test-t", "trigger-some-error-here"); The error message they receive will be about So, not a bandaid, but necessary to solve the problem of bad error messages for library authors. |
Maybe my choice of the word "band-aid" was poor, and if there was any implication I'm anything but fully supportive of this idea, that wasn't my intent. I only meant to suggest that error reporting in general needs some love, and while fixing displayed aliases is absolutely a part of that, there may be opportunity to improve things in other ways as well. No argument from me that diagnostic messages need to have better control over how and when aliases are expanded, just there's more work to be done too[1] :) 1: stuff like omitting intermediary expansions would be nice ( |
So, I know not many people deal with types hundreds of lines long but I figured I'd share a workable solution for some cases. In workaround 1 listed above, we had a type alias already handy. It is hacky and forces the responsibility of readability onto downstream users, rather than library authors, but it's better than nothing... The solution has the following form, //Imagine `foo()` is a generic function returning a type hundreds of lines long.
const _x = foo(/*args*/);
type Id<T> = T;
export interface X extends Id<typeof _x> {
}
export const x : X = _x; Every reference to Here is an example of the benefits of this approach, Before
|
This is a huge issue for any library that deals with large union-types. I have a library which exposes an AST which is—naturally—a union type. Throughout the library functions are generally overloaded to take either an AST node or a shorthand, but the implementation of the AST node ends up being 99% of the error. See this method: where(clause: { [K in keyof Table]?: Table[K] } | Expr<Ext>) which produces this rather unwieldy error message when the user has a typo:
The only part actually relevant to the user is the last part where it lists the allowed properties. If there were a way to stop TypedAst from expanding the error message would be much more useful:
This one is particularly bad because I'd carrying around "phantom" type information but even excluding that it's still not great:
|
Merging #42075, which asks more specifically about not flattening nested union/intersection types, into this. |
I just wanted to say that I love you all for all the work you all do on TS <3 Happy New Year! |
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,
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
andinterface
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,
If
PleaseDoNotExpand<>
were aclass
orinterface
, we would haveconst b: PleaseDoNotExpand<number>|undefined
Use Cases
What do you want to use this for?
.d.ts
filesSome 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
#34777
#34777 (comment)
The idea is to use an
interface
to extend the type alias. From that point, only theinterface
'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
#35616
#35616 (comment)
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
Checklist
My suggestion meets these guidelines:
Related
#34556 asks to never alias a type. This asks to always alias a type.
The text was updated successfully, but these errors were encountered: