-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Type aliases and other changes in language specification #998
Conversation
Could you clarify why a type is allowed to reference a union of a primitive and an anonymous type but not an anonymous type by itself? type Text = string | { text: string }; // <-- example in spec
type Point = { x: number; y: number; }; // <-- explicitly disallowed by spec |
A type alias for an anonymous object type literal: type Point = { x: number; y: number; }; would be almost the same as an interface declaration: interface Point { x: number; y: number; } The subtle differences include:
We feel that the confusion caused by these differences are better avoided. |
I should add that an alternative to the error would be to just treat type alias declarations for object type literals as equivalent to interface declarations. We discussed doing that, but we thought it would be odd for such type alias declarations to merge with other type alias or interface declarations for the same name. But disallowing merging would then again make them different from interfaces. Anyway, I could be persuaded we should just say type aliases for object type literals are exactly equivalent to interface declarations, merging and all. |
I understand that there are subtle semantic differences (which could be employed to create an interface type which can't be extended) but I feel we don't need to have an arbitrary restriction in order to sidestep some perceived difficulty down the line. All I can do is urge you to reconsider. If you don't reconsider, I can always work around the restriction by defining type Point = { x: number; y: number; } | { x: number; y: number; }; 😉 |
We didn't really like the carve-out for object types either, but it seems better than the alternatives. Are there specific use cases where you want to write |
Mainly philosophical, given that it's supported as part of a union. I don't think type aliases should be extendable. In that sense Another thing to consider is function types: type fun = () => number; // allowed
type fun = { // not allowed?
(): number;
(_:string): string;
}; By disallowing the second one, the ability to declare overloaded function type aliases is restricted to exported interfaces only. |
The danger is that if we don't make That's the same reason we allowed shorthand function types as alias targets -- |
I understand your reasons. I still disagree. I have to argue that .d.ts authors would be, in general, more technical than .d.ts users, and could be trusted to use type v.s. interface effectively. But I don't think I can contribute any more to this discussion. Thanks for your time. |
@sparecycles @RyanCavanaugh I do agree it is an odd restriction and one that we're not particularly fond of. I'm starting to think we should just allow it and note the differences (and their consequences) in the Language Specification. |
The original objection to this was that if we wanted to annotate the parameter in the following function: function foo(point: { x: number; y: number }){} with the introduction of type aliases, there would be three ways of annotating // A. Using an interface
interface Point {
x: number;
y: number;
}
// B. Using typeof with type aliases
var point: { x: number; y: number };
type Point = typeof point;
// C. Directly declaring with type aliases
type Point = { x: number; y: number }; All three methods of declaring My specific objection is that in an organisation, when there is more than one way to accomplish the same thing, we end up having half the people using one mechanism and the other half the other. Then we have situations of people correcting each other's coding styles in an endless death spiral 😃 . This is of course why linters exist, but I was arguing for the problem to be resolved at source. However, in this case, since only the third mechanism is prohibited, it doesn't really achieve the desired result in any case. My preference is for type aliases to be restricted to union and tuple types: type u = string|number|{n: string};
type t = [number, number];
type s = string; // error We have lived without type aliases for two years now without a significant demand from the community. |
I think we have to ask ourselves, are we adding type aliases to fill a particular need with union and tuple types, or are we extending typescript with a new feature? |
I think we just have to accept the overlap between interface declarations and type alias declarations. The key observation here is that an alias is _exactly_ the same thing as writing the type it aliases, and since anonymous object type literals overlap with interface declarations, then so do aliases to object type literals. At the end of the day, a lot of this comes down to the fact that we have a structural type system and that there will always be multiple ways to write a particular type. I completely agree that it would ideal not to have this overlap. However, artificial restrictions seem like a cure that's worse than the disease, so I think we should drop them for now. |
Updated Type Alias section in the spec to describe differences between interfaces and type aliases for object type literals. |
@ahejlsberg The updated section looks great! |
Type aliases and other changes in language specification
How about type alias with type parameters?
|
@danielearwicker Can you log a separate suggestion issue for that? Thanks! |
Logged #1616 Type aliases requiring type parameters |
Changes in this PR: