-
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 #957
Type aliases #957
Conversation
Compiling this file with module M {
type W = Window|string;
export module N {
export class Window { }
export var p: W;
}
} |
I've translated this specification draft into French here: http://bit.ly/1wvcqyD |
Are interfaces effectively a type alias to an object type then? i.e. is there any different between:
Also, because the right side is a type, i feel that a type annotation would be a better syntax. i.e.:
Finally, are type aliases allowed inside classes/interfaces? For example, can i do:
If we don't allow this, then it's not possible to create a type alias to a type that contains a type parameter (which seems unfortunate). ? |
One difference is that as currently implemented the interface Type aliases can't be parameterized so you'd have no way to say what |
@danquirk Good points. Is there a good reason though to not emit aliases in the .d.ts? Presumably this helps with code clarity on the implementation side, and i would think you would want that in the .d.ts side as well. I definitely get the issue with being unable to instantiate type aliases. However, i still think they would be useful in a class/interface setting, esp. when describing large and unwieldy types (esp those that use type parameters and uninions). |
I tend to agree on the .d.ts front. If you wrote them for clarity in your own code you likely want them in your API, people already use interfaces and classes this way for various things (ex naming certain call signatures). The other difference that I didn't mention before is the original motivation where you can't use an interface to name a set of union types. On the second point, you can use type aliases for instantiated generics, just not open ones. We discussed it for awhile in the design meeting, it wasn't clear there was a great solution for something that does have a workaround (just use |
@CyrusNajmabadi From a semantic point of view there is no difference between an interface and a type alias for a similar object type (or any other structurally identical type for that matter). You will however see different behavior in error messages and quick info because, just like import aliases, type aliases are always "dereferenced" to the aliased type, whereas interfaces are displayed by their name. One way to think of it is that type aliases are just names for (possibly anonymous) types, whereas interfaces are a kind of type (that always has a name). I don't think using type annotation syntax (i.e. colon) is better. We're not saying that a type alias is something whose type is blah, where saying that it is an alias for blah. I think equals expresses that better and we already use it for import aliases. Once we implement local type declarations in functions and methods (which we need to support ES6 style local classes) you'll be able to reference type parameters. Whether to also allow local type declarations in class and interface bodies is a different question. Not sure about that. |
BTW, another difference between interfaces and type aliases is that interfaces support merged declarations whereas type aliases do not. |
Regarding local types in class and interface bodies: I would think this is only useful if you have a lot of private methods in your class, and they would benefit from the presence of a utility type. For example: class C {
interface PrivateType {
// stuff
}
private doStuff1(p: PrivateType): PrivateType {
...
}
private doStuff2(p: PrivateType): PrivateType {
...
}
public publicMethod(p: PrivateType) { // Error in --declaration mode
var p2: PrivateType; // ok inside the method
}
} The idea is kind of like an existential type. The class knows about a type, that others need not know about, but it is useful to defined it internally to be shared by all its private methods. |
Code looks good, but let's finish addressing the remaining design questions before taking this. |
Just pushed some changes to add support for type aliases in .d.ts files. I think we need @sheetalkamat to look at the issue @RyanCavanaugh points out above. Best I can tell the .d.ts generation code makes assumptions about scopes and visibility of named types that can be circumvented with type aliases. But a deeper issue is that when a declaration has a type annotation, the .d.ts generator doesn't emit what was in the annotation but rather emits from the resolved Type object. The latter is much harder to get right and doesn't "see" type aliases because they have already been resolved to the aliased type. For example: module M {
export type W = number | string;
export module N {
export var p: W;
}
} generates the following .d.ts file: declare module M {
type W = string | number;
module N {
var p: string | number;
}
} Note that the type alias has been unrolled to the aliased type. I don't think we want that. Instead, when type annotations are present we should always emit them exactly as written, and we should only emit from Type objects when we're dealing with inferred types. |
I think the introduction of type aliases is unfortunate, since we now have two ways of declaring types: var fullName: {
first: string;
middle: string;
last: string;
};
type Name = typeof fullName;
// Same thing
interface Name {
first: string;
middle: string;
last: string;
}; Why not just cover the new scenarios introduced by union types and tuples using interfaces? interface StringOrNumber extends string | number {
}
interface Point extends [number, number] {
} |
There's actually a subtle distinction here. Interfaces are always "object types" (never primitives or union types), and declaring an Allowing an interface to extend a primitive type or a union type would open up a very weird can of worms. We'd have to drastically change what |
@NoelAbrahams I think you have a valid point, but as @RyanCavanaugh points out, primitive types and union types aren't object types and it simply isn't meaningful to extend them. The best we could do is to use the interface Name {
first: string;
middle: string;
last: string;
}
interface StringOrNumber = string | number; I'm sort of on the fence about it. I like that there isn't a new keyword, but I find it a bit odd to use |
Just as a suggestion, maybe replacing interface by type keyword: type Name {
first: string;
middle: string;
last: string;
}
type StringOrNumber = string | number; |
@yahiko00 Since we obviously can't take away the |
that may very well be true. It's not something that seems to be terribly wrong IMO. Also, since the following is possible: interface MyString extends String {
}
var foo : MyString = 'foo'; // okay why not interface StringOrNumber extends String | Number {
}
var foo : StringOrNumber = 'foo'; |
@NoelAbrahams It simply isn't meaningful to extend a union type, so we'd have to require the |
@NoelAbrahams In your example, if we keep the current meaning of extends, but allow it on union types, StringOrNumber would be equivalent to |
Documenting the endpoint of the very long conversation we had today spurred by @NoelAbrahams' suggestion of not adding a new keyword. Our approach was basically to talk only about the desired semantics of type aliases, and then decide the keyword based on what the behavior of those rules were. First, we want the following to be valid (disregard whether we use
We do not want this to be valid.
The reason for not wanting the last declaration is that someone might write The proposal we ended up with is that a type alias is only a valid declaration if it it takes one of the forms listed above (or an equivalent parenthesization thereof). We briefly discussed making Other basic things to note:
|
@RyanCavanaugh's comment: The type alias would also have to be in scope at the place we want to refer to it. If not, we may emit a privacy mismatch error. |
Based on our discussion I just pushed a change that makes it an error to alias an object type literal: type Point = { // Error: Aliased type cannot be an object type literal. Use an interface declaration instead.
x: number;
y: number;
}; We decided to stick with the |
What's the rationale for not emitting the alias if it was inferred? |
👍 thanks for considering the point. I think the proposed solution is a decent compromise. A clarification: in permitted case
is the type |
@NoelAbrahams The |
This is probably a stupid question but are two named types sharing a same definition compatible? type A = number[];
function fct(a: A) { ... }
type B = number[];
var b:B;
...
fct(b); // is it a valid call? |
Yes. Type aliases won't affect compatibility in any way. |
Does permitting
The role of |
@NoelAbrahams The new thing is that you can declare aliases for types (but the aliases aren't actually a new kind of type). In your example above, referencing the alias |
Yes, thanks. That's quite clear now. I suppose for completeness the syntax needs to accommodate all cases, but from the perspective of how we write our code I think we might just discourage use of the following cases: type s = typeof SomeClass;
type p = string;
type r = Date; since it doesn't make sense to call a spade anything but 😃 |
Type aliases allow names to be associated with arbitrary types.
Type aliases are particuarly useful for naming types that are not object types, such as union types or primitive types.
Type aliases can alias instantiations of generic types (as in the
NameLookup
type above), but they cannot themselves have type parameters.Just like interface declarations, type aliases declare only types and not values. For example:
An error occurs on
new StringList()
because there is no constructor function named StringList. Only a type by that name exists. To get both, a subclass can be declared: