From 9c6e6acf973b394b3f29608a5caabb2c0bdeb425 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 5 Jun 2015 14:28:57 -0700 Subject: [PATCH] Support generic type aliases --- src/compiler/binder.ts | 2 + src/compiler/checker.ts | 144 ++++++++++++++++++++++------------------ src/compiler/parser.ts | 2 + src/compiler/types.ts | 5 +- 4 files changed, 89 insertions(+), 64 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index ea589891241..9f7f36d10d0 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -338,6 +338,7 @@ module ts { case SyntaxKind.ArrowFunction: case SyntaxKind.ModuleDeclaration: case SyntaxKind.SourceFile: + case SyntaxKind.TypeAliasDeclaration: return ContainerFlags.IsContainerWithLocals; case SyntaxKind.CatchClause: @@ -424,6 +425,7 @@ module ts { case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: + case SyntaxKind.TypeAliasDeclaration: // 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 diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 28242aba015..40e98b44148 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2577,8 +2577,8 @@ module ts { function getLocalTypeParametersOfClassOrInterface(symbol: Symbol): TypeParameter[] { let result: TypeParameter[]; for (let node of symbol.declarations) { - if (node.kind === SyntaxKind.InterfaceDeclaration || node.kind === SyntaxKind.ClassDeclaration) { - let declaration = node; + if (node.kind === SyntaxKind.InterfaceDeclaration || node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.TypeAliasDeclaration) { + let declaration = node; if (declaration.typeParameters) { result = appendTypeParameters(result, declaration.typeParameters); } @@ -2593,6 +2593,15 @@ module ts { return concatenate(getOuterTypeParametersOfClassOrInterface(symbol), getLocalTypeParametersOfClassOrInterface(symbol)); } + function getTypeParametersOfTypeAlias(symbol: Symbol): TypeParameter[] { + let links = getSymbolLinks(symbol); + if (!links.typeParameters) { + let declaration = getDeclarationOfKind(symbol, SyntaxKind.TypeAliasDeclaration); + links.typeParameters = declaration.typeParameters ? appendTypeParameters(undefined, declaration.typeParameters) : emptyArray; + } + return links.typeParameters; + } + function getBaseTypes(type: InterfaceType): ObjectType[] { let typeWithBaseTypes = type; if (!typeWithBaseTypes.baseTypes) { @@ -3513,72 +3522,79 @@ module ts { } } - function getTypeFromTypeReferenceOrExpressionWithTypeArguments(node: TypeReferenceNode | ExpressionWithTypeArguments): Type { - let links = getNodeLinks(node); - if (!links.resolvedType) { - let type: Type; - - // We don't currently support heritage clauses with complex expressions in them. - // For these cases, we just set the type to be the unknownType. - if (node.kind !== SyntaxKind.ExpressionWithTypeArguments || isSupportedExpressionWithTypeArguments(node)) { - let typeNameOrExpression = node.kind === SyntaxKind.TypeReference - ? (node).typeName - : (node).expression; - - let symbol = resolveEntityName(typeNameOrExpression, SymbolFlags.Type); - if (symbol) { - if ((symbol.flags & SymbolFlags.TypeParameter) && isTypeParameterReferenceIllegalInConstraint(node, symbol)) { - // TypeScript 1.0 spec (April 2014): 3.4.1 - // Type parameters declared in a particular type parameter list - // may not be referenced in constraints in that type parameter list - // Implementation: such type references are resolved to 'unknown' type that usually denotes error - type = unknownType; - } - else { - type = createTypeReferenceIfGeneric( - getDeclaredTypeOfSymbol(symbol), - node, node.typeArguments); - } - } + function getTypeFromClassOrInterfaceReference(node: TypeReferenceNode | ExpressionWithTypeArguments, symbol: Symbol): Type { + let type = getDeclaredTypeOfSymbol(symbol); + let typeParameters = (type).localTypeParameters; + if (typeParameters) { + if (!node.typeArguments || node.typeArguments.length !== typeParameters.length) { + error(node, Diagnostics.Generic_type_0_requires_1_type_argument_s, typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType), typeParameters.length); + return unknownType; } - - links.resolvedType = type || unknownType; - } - - return links.resolvedType; - } - - function createTypeReferenceIfGeneric(type: Type, node: Node, typeArguments: NodeArray): Type { - if (type.flags & (TypeFlags.Class | TypeFlags.Interface) && type.flags & TypeFlags.Reference) { // In a type reference, the outer type parameters of the referenced class or interface are automatically // supplied as type arguments and the type reference only specifies arguments for the local type parameters // of the class or interface. - let localTypeParameters = (type).localTypeParameters; - let expectedTypeArgCount = localTypeParameters ? localTypeParameters.length : 0; - let typeArgCount = typeArguments ? typeArguments.length : 0; - if (typeArgCount === expectedTypeArgCount) { - // When no type arguments are expected we already have the right type because all outer type parameters - // have themselves as default type arguments. - if (typeArgCount) { - return createTypeReference(type, concatenate((type).outerTypeParameters, - map(typeArguments, getTypeFromTypeNode))); - } - } - else { - error(node, Diagnostics.Generic_type_0_requires_1_type_argument_s, typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType), expectedTypeArgCount); - return undefined; - } + return createTypeReference(type, concatenate((type).outerTypeParameters, + map(node.typeArguments, getTypeFromTypeNode))); } - else { - if (typeArguments) { - error(node, Diagnostics.Type_0_is_not_generic, typeToString(type)); - return undefined; - } + if (node.typeArguments) { + error(node, Diagnostics.Type_0_is_not_generic, typeToString(type)); + return unknownType; } - return type; } + function getTypeFromTypeAliasReference(node: TypeReferenceNode | ExpressionWithTypeArguments, symbol: Symbol): Type { + let type = getDeclaredTypeOfSymbol(symbol); + let typeParameters = getTypeParametersOfTypeAlias(symbol); + if (typeParameters.length) { + if (!node.typeArguments || node.typeArguments.length !== typeParameters.length) { + error(node, Diagnostics.Generic_type_0_requires_1_type_argument_s, symbolToString(symbol), typeParameters.length); + return unknownType; + } + let typeArguments = map(node.typeArguments, getTypeFromTypeNode); + let id = getTypeListId(typeArguments); + let links = getSymbolLinks(symbol); + if (!links.instantiations) { + links.instantiations = {}; + // The declared type corresponds to an instantiation with its own type parameters as type arguments + links.instantiations[getTypeListId(typeParameters)] = type; + } + return links.instantiations[id] || (links.instantiations[id] = instantiateType(type, createTypeMapper(typeParameters, typeArguments))); + } + if (node.typeArguments) { + error(node, Diagnostics.Type_0_is_not_generic, symbolToString(symbol)); + return unknownType; + } + return type; + } + + function getTypeFromTypeParameterReference(node: TypeReferenceNode | ExpressionWithTypeArguments, symbol: Symbol): Type { + // TypeScript 1.0 spec (April 2014): 3.4.1 + // Type parameters declared in a particular type parameter list + // may not be referenced in constraints in that type parameter list + // Implementation: such type references are resolved to 'unknown' type that usually denotes error + return isTypeParameterReferenceIllegalInConstraint(node, symbol) ? unknownType : getDeclaredTypeOfSymbol(symbol); + } + + function getTypeFromTypeReference(node: TypeReferenceNode | ExpressionWithTypeArguments): Type { + let links = getNodeLinks(node); + if (!links.resolvedType) { + // We only support expressions that are simple qualified names. For other expressions this produces undefined. let typeNameOrExpression = node.kind === SyntaxKind.TypeReference ? (node).typeName : + isSupportedExpressionWithTypeArguments(node) ? (node).expression : + undefined; + let symbol = typeNameOrExpression && resolveEntityName(typeNameOrExpression, SymbolFlags.Type) || unknownSymbol; + let type = symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface) ? getTypeFromClassOrInterfaceReference(node, symbol) : + symbol.flags & SymbolFlags.TypeParameter ? getTypeFromTypeParameterReference(node, symbol) : + symbol.flags & SymbolFlags.TypeAlias ? getTypeFromTypeAliasReference(node, symbol) : + getDeclaredTypeOfSymbol(symbol); + // Cache both the resolved symbol and the resolved type. The resolved symbol is needed in when we check the + // type reference in checkTypeReferenceOrExpressionWithTypeArguments. + links.resolvedSymbol = symbol; + links.resolvedType = type; + } + return links.resolvedType; + } + function getTypeFromTypeQueryNode(node: TypeQueryNode): Type { let links = getNodeLinks(node); if (!links.resolvedType) { @@ -3848,9 +3864,9 @@ module ts { case SyntaxKind.StringLiteral: return getTypeFromStringLiteral(node); case SyntaxKind.TypeReference: - return getTypeFromTypeReferenceOrExpressionWithTypeArguments(node); + return getTypeFromTypeReference(node); case SyntaxKind.ExpressionWithTypeArguments: - return getTypeFromTypeReferenceOrExpressionWithTypeArguments(node); + return getTypeFromTypeReference(node); case SyntaxKind.TypeQuery: return getTypeFromTypeQueryNode(node); case SyntaxKind.ArrayType: @@ -8766,13 +8782,15 @@ module ts { // Grammar checking checkGrammarTypeArguments(node, node.typeArguments); - let type = getTypeFromTypeReferenceOrExpressionWithTypeArguments(node); + let type = getTypeFromTypeReference(node); if (type !== unknownType && node.typeArguments) { // Do type argument local checks only if referenced type is successfully resolved + let symbol = getNodeLinks(node).resolvedSymbol; + let typeParameters = symbol.flags & SymbolFlags.TypeAlias ? getTypeParametersOfTypeAlias(symbol) : (type).target.localTypeParameters; let len = node.typeArguments.length; for (let i = 0; i < len; i++) { checkSourceElement(node.typeArguments[i]); - let constraint = getConstraintOfTypeParameter((type).target.typeParameters[i]); + let constraint = getConstraintOfTypeParameter(typeParameters[i]); if (produceDiagnostics && constraint) { let typeArgument = (type).typeArguments[i]; checkTypeAssignableTo(typeArgument, constraint, node, Diagnostics.Type_0_does_not_satisfy_the_constraint_1); diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 80c70a250fb..e15c6184e88 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -255,6 +255,7 @@ module ts { return visitNodes(cbNodes, node.decorators) || visitNodes(cbNodes, node.modifiers) || visitNode(cbNode, (node).name) || + visitNodes(cbNodes, (node).typeParameters) || visitNode(cbNode, (node).type); case SyntaxKind.EnumDeclaration: return visitNodes(cbNodes, node.decorators) || @@ -4547,6 +4548,7 @@ module ts { setModifiers(node, modifiers); parseExpected(SyntaxKind.TypeKeyword); node.name = parseIdentifier(); + node.typeParameters = parseTypeParameters(); parseExpected(SyntaxKind.EqualsToken); node.type = parseType(); parseSemicolon(); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index d4e1378772b..4f37869b5bf 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -939,6 +939,7 @@ module ts { export interface TypeAliasDeclaration extends Declaration, Statement { name: Identifier; + typeParameters?: NodeArray; type: TypeNode; } @@ -1525,7 +1526,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 (empty array if none) + instantiations?: Map; // Instantiations of generic type alias 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