Skip to content
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

Generic type aliases #3397

Merged
merged 8 commits into from
Jun 9, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 12 additions & 9 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@ module ts {
case SyntaxKind.ArrowFunction:
case SyntaxKind.ModuleDeclaration:
case SyntaxKind.SourceFile:
case SyntaxKind.TypeAliasDeclaration:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this a container with locals, but classes and interfaces are not?

return ContainerFlags.IsContainerWithLocals;

case SyntaxKind.CatchClause:
Expand Down Expand Up @@ -385,10 +386,10 @@ module ts {

function declareSymbolAndAddToSymbolTableWorker(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags): Symbol {
switch (container.kind) {
// Modules, source files, and classes need specialized handling for how their
// Modules, source files, and classes need specialized handling for how their
// members are declared (for example, a member of a class will go into a specific
// symbol table depending on if it is static or not). As such, we defer to
// specialized handlers to take care of declaring these child members.
// symbol table depending on if it is static or not). We defer to specialized
// handlers to take care of declaring these child members.
case SyntaxKind.ModuleDeclaration:
return declareModuleMember(node, symbolFlags, symbolExcludes);

Expand All @@ -406,9 +407,10 @@ module ts {
case SyntaxKind.ObjectLiteralExpression:
case SyntaxKind.InterfaceDeclaration:
// Interface/Object-types always have their children added to the 'members' of
// their container. They are only accessible through an instance of their
// container, and are never in scope otherwise (even inside the body of the
// object / type / interface declaring them).
// their container. They are only accessible through an instance of their
// container, and are never in scope otherwise (even inside the body of the
// object / type / interface declaring them). An exception is type parameters,
// which are in scope without qualification (similar to 'locals').
return declareSymbol(container.symbol.members, container.symbol, node, symbolFlags, symbolExcludes);

case SyntaxKind.FunctionType:
Expand All @@ -424,11 +426,12 @@ module ts {
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.FunctionExpression:
case SyntaxKind.ArrowFunction:
case SyntaxKind.TypeAliasDeclaration:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this get its type parameters added to .locals, but Class and Interface do not? Are the type parameters added to the locals for generic signatures as well?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type parameters of classes and interfaces are in the members collection, whereas type parameters of generic signatures are in the locals collection. Since a type alias doesn't have any notion of members, locals seems like the best choice. Also, it is a bit less work in resolveName because we don't have to add an extra case to the switch (we look for locals on every ancestor node).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we move the class and interface type parameters to the locals as well (and create a locals if we don't have one already)? Seems really strange that they are stored in the members, and certainly not consistent with type aliases.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I'd like to leave it this way. We wouldn't gain anything from the move, in fact it would just introduce another symbol table that needs to be visited. Also, the spec actually calls for type parameters and members to be in the same declaration space (even though it isn't observable right now because all members are values, but it would be if we ever support local types in classes and/or interfaces).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I always thought of locals as entities that could be accessed without qualification (they are in scope), whereas members require qualification (they are not in scope). This is why it seemed natural that type parameters would be locals, while class elements would be members. If that is not the difference between locals and members, what is the difference?

// All the children of these container types are never visible through another
// symbol (i.e. through another symbol's 'exports' or 'members'). Instead,
// they're only accessed 'lexically' (i.e. from code that exists underneath
// symbol (i.e. through another symbol's 'exports' or 'members'). Instead,
// they're only accessed 'lexically' (i.e. from code that exists underneath
// their container in the tree. To accomplish this, we simply add their declared
// symbol to the 'locals' of the container. These symbols can then be found as
// symbol to the 'locals' of the container. These symbols can then be found as
// the type checker walks up the containers, checking them for matching names.
return declareSymbol(container.locals, undefined, node, symbolFlags, symbolExcludes);
}
Expand Down
162 changes: 94 additions & 68 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ module ts {
return visitNodes(cbNodes, node.decorators) ||
visitNodes(cbNodes, node.modifiers) ||
visitNode(cbNode, (<TypeAliasDeclaration>node).name) ||
visitNodes(cbNodes, (<TypeAliasDeclaration>node).typeParameters) ||
visitNode(cbNode, (<TypeAliasDeclaration>node).type);
case SyntaxKind.EnumDeclaration:
return visitNodes(cbNodes, node.decorators) ||
Expand Down Expand Up @@ -4591,6 +4592,7 @@ module ts {
setModifiers(node, modifiers);
parseExpected(SyntaxKind.TypeKeyword);
node.name = parseIdentifier();
node.typeParameters = parseTypeParameters();
parseExpected(SyntaxKind.EqualsToken);
node.type = parseType();
parseSemicolon();
Expand Down
5 changes: 4 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -946,6 +946,7 @@ module ts {

export interface TypeAliasDeclaration extends Declaration, Statement {
name: Identifier;
typeParameters?: NodeArray<TypeParameterDeclaration>;
type: TypeNode;
}

Expand Down Expand Up @@ -1539,7 +1540,9 @@ module ts {
export interface SymbolLinks {
target?: Symbol; // Resolved (non-alias) target of an alias
type?: Type; // Type of value symbol
declaredType?: Type; // Type of class, interface, enum, or type parameter
declaredType?: Type; // Type of class, interface, enum, type alias, or type parameter
typeParameters?: TypeParameter[]; // Type parameters of type alias (undefined if non-generic)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i would call this typeAliasTypeParameter. That way it's clear from use that it's only intended for Type aliases. "typeParameters" on its own is very vague, and makes it unclear at the use site that it's only for these cases, and not for anything else with type parameters. (same with instantiations).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we either keep it this way or switch to a family of xxxSymbolLinks types like we have for types. But my preference would be to just keep it this way to reduce the noise.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not be opposed to starting a small family of xxxSymbolLinks types. I actually think that would be a rather nice way of compartmentalizing things.

instantiations?: Map<Type>; // Instantiations of generic type alias (undefined if non-generic)
mapper?: TypeMapper; // Type mapper for instantiation alias
referenced?: boolean; // True if alias symbol has been referenced as a value
unionType?: UnionType; // Containing union type for union property
Expand Down
120 changes: 120 additions & 0 deletions tests/baselines/reference/genericTypeAliases.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
//// [genericTypeAliases.ts]
type Tree<T> = T | { left: Tree<T>, right: Tree<T> };

var tree: Tree<number> = {
left: {
left: 0,
right: {
left: 1,
right: 2
},
},
right: 3
};

type Lazy<T> = T | (() => T);

var ls: Lazy<string>;
ls = "eager";
ls = () => "lazy";

type Foo<T> = T | { x: Foo<T> };
type Bar<U> = U | { x: Bar<U> };

// Deeply instantiated generics
var x: Foo<string>;
var y: Bar<string>;
x = y;
y = x;

x = "string";
x = { x: "hello" };
x = { x: { x: "world" } };

var z: Foo<number>;
z = 42;
z = { x: 42 };
z = { x: { x: 42 } };

type Strange<T> = string; // Type parameter not used
var s: Strange<number>;
s = "hello";

interface Tuple<A, B> {
a: A;
b: B;
}

type Pair<T> = Tuple<T, T>;

interface TaggedPair<T> extends Pair<T> {
tag: string;
}

var p: TaggedPair<number>;
p.a = 1;
p.b = 2;
p.tag = "test";

function f<A>() {
type Foo<T> = T | { x: Foo<T> };
var x: Foo<A[]>;
return x;
}

function g<B>() {
type Bar<U> = U | { x: Bar<U> };
var x: Bar<B[]>;
return x;
}

// Deeply instantiated generics
var a = f<string>();
var b = g<string>();
a = b;


//// [genericTypeAliases.js]
var tree = {
left: {
left: 0,
right: {
left: 1,
right: 2
}
},
right: 3
};
var ls;
ls = "eager";
ls = function () { return "lazy"; };
// Deeply instantiated generics
var x;
var y;
x = y;
y = x;
x = "string";
x = { x: "hello" };
x = { x: { x: "world" } };
var z;
z = 42;
z = { x: 42 };
z = { x: { x: 42 } };
var s;
s = "hello";
var p;
p.a = 1;
p.b = 2;
p.tag = "test";
function f() {
var x;
return x;
}
function g() {
var x;
return x;
}
// Deeply instantiated generics
var a = f();
var b = g();
a = b;
Loading