From fc450a2d2f6ee7e4e5768eb27aa921ccb5cb9f10 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 4 Nov 2016 14:17:51 -0700 Subject: [PATCH] Introduce MappedType in type checker --- src/compiler/checker.ts | 103 ++++++++++++++++++++++++++++++++++++---- src/compiler/types.ts | 18 +++++-- 2 files changed, 107 insertions(+), 14 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9e1a0b6ec7b..460f0f449f5 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2246,6 +2246,9 @@ namespace ts { else if (getObjectFlags(type) & ObjectFlags.Anonymous) { writeAnonymousType(type, nextFlags); } + else if (getObjectFlags(type) & ObjectFlags.Mapped) { + writeMappedType(type); + } else if (type.flags & TypeFlags.StringOrNumberLiteral) { writer.writeStringLiteral(literalTypeToString(type)); } @@ -2536,6 +2539,32 @@ namespace ts { writePunctuation(writer, SyntaxKind.CloseBraceToken); inObjectTypeLiteral = saveInObjectTypeLiteral; } + + function writeMappedType(type: MappedType) { + const constraintType = getConstraintTypeFromMappedType(type); + if (constraintType.flags & (TypeFlags.TypeParameter | TypeFlags.Index)) { + writePunctuation(writer, SyntaxKind.OpenBraceToken); + writer.writeLine(); + writer.increaseIndent(); + writePunctuation(writer, SyntaxKind.OpenBracketToken); + appendSymbolNameOnly((type.target || type).typeParameter.symbol, writer); + writeSpace(writer); + writeKeyword(writer, SyntaxKind.InKeyword); + writeSpace(writer); + writeType(constraintType, TypeFormatFlags.None); + writePunctuation(writer, SyntaxKind.CloseBracketToken); + writePunctuation(writer, SyntaxKind.ColonToken); + writeSpace(writer); + writeType(getTemplateTypeFromMappedType(type), TypeFormatFlags.None); + writePunctuation(writer, SyntaxKind.SemicolonToken); + writer.writeLine(); + writer.decreaseIndent(); + writePunctuation(writer, SyntaxKind.CloseBraceToken); + } + else { + writeLiteralType(type, TypeFormatFlags.None); + } + } } function buildTypeParameterDisplayFromSymbol(symbol: Symbol, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags) { @@ -4408,6 +4437,46 @@ namespace ts { } } + function forEachType(type: Type, f: (t: Type) => T): T { + return type.flags & TypeFlags.Union ? forEach((type).types, f) : f(type); + } + + // { [P in K]: T } + // Get apparent type of K + // If apparent type is a 'keyof T', get apparent type of T + // For each constituent literal type U + // create mapper from P to U + // instantiate T using mapper + // if U is string or number, create index signature with instantiated type + // otherwise create property with name from U and instantiated type + function resolveMappedTypeMembers(type: MappedType) { + const members: SymbolTable = createMap(); + let stringIndexInfo: IndexInfo; + let numberIndexInfo: IndexInfo; + const target = type.target || type; + const keyType = getApparentType(getConstraintTypeFromMappedType(type)); + const iterationType = keyType.flags & TypeFlags.Index ? getIndexType(getApparentType((keyType).type)) : keyType; + forEachType(iterationType, t => { + const iterationMapper = createUnaryTypeMapper(target.typeParameter, t); + const templateMapper = type.mapper ? combineTypeMappers(type.mapper, iterationMapper) : iterationMapper; + if (t.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral | TypeFlags.EnumLiteral)) { + const propName = (t).text; + const prop = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, propName); + prop.type = instantiateType(target.templateType, templateMapper); + members[propName] = prop; + } + }) + setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo); + } + + function getConstraintTypeFromMappedType(type: MappedType) { + return instantiateType(getConstraintOfTypeParameter((type.target || type).typeParameter), type.mapper || identityMapper); + } + + function getTemplateTypeFromMappedType(type: MappedType) { + return instantiateType((type.target || type).templateType, type.mapper || identityMapper); + } + function resolveStructuredTypeMembers(type: StructuredType): ResolvedType { if (!(type).members) { if (type.flags & TypeFlags.Object) { @@ -4420,6 +4489,9 @@ namespace ts { else if ((type).objectFlags & ObjectFlags.Anonymous) { resolveAnonymousTypeMembers(type); } + else if ((type).objectFlags & ObjectFlags.Mapped) { + resolveMappedTypeMembers(type); + } } else if (type.flags & TypeFlags.Union) { resolveUnionTypeMembers(type); @@ -5834,14 +5906,16 @@ namespace ts { return links.resolvedType; } - function getTypeFromMappedTypeNode(node: MappedTypeNode) { + function getTypeFromMappedTypeNode(node: MappedTypeNode, aliasSymbol?: Symbol, aliasTypeArguments?: Type[]): Type { const links = getNodeLinks(node); if (!links.resolvedType) { - getTypeFromTypeNode(node.typeParameter.constraint); - if (node.type) { - getTypeFromTypeNode(node.type); - } - links.resolvedType = unknownType; + const type = createObjectType(ObjectFlags.Mapped); + type.declaration = node; + type.typeParameter = getDeclaredTypeOfTypeParameter(getSymbolOfNode(node.typeParameter)); + type.templateType = node.type ? getTypeFromTypeNode(node.type) : anyType; + type.aliasSymbol = aliasSymbol; + type.aliasTypeArguments = aliasTypeArguments; + links.resolvedType = type; } return links.resolvedType; } @@ -6015,7 +6089,7 @@ namespace ts { case SyntaxKind.IndexedAccessType: return getTypeFromIndexedAccessTypeNode(node); case SyntaxKind.MappedType: - return getTypeFromMappedTypeNode(node); + return getTypeFromMappedTypeNode(node, aliasSymbol, aliasTypeArguments); // This function assumes that an identifier or qualified name is a type expression // Callers should first ensure this by calling isTypeNode case SyntaxKind.Identifier: @@ -6180,7 +6254,13 @@ namespace ts { return result; } - function instantiateAnonymousType(type: AnonymousType, mapper: TypeMapper): ObjectType { + function instantiateAnonymousOrMappedType(type: AnonymousType | MappedType, mapper: TypeMapper): ObjectType { + if (type.objectFlags & ObjectFlags.Instantiated) { + // If the type being instantiated is itself a instantiation, fetch the original target and + // combine the type mappers. + mapper = combineTypeMappers(type.mapper, mapper); + type = type.target; + } if (mapper.instantiations) { const cachedType = mapper.instantiations[type.id]; if (cachedType) { @@ -6191,7 +6271,7 @@ namespace ts { mapper.instantiations = []; } // Mark the anonymous type as instantiated such that our infinite instantiation detection logic can recognize it - const result = createObjectType(ObjectFlags.Anonymous | ObjectFlags.Instantiated, type.symbol); + const result = createObjectType(type.objectFlags | ObjectFlags.Instantiated, type.symbol); result.target = type; result.mapper = mapper; result.aliasSymbol = type.aliasSymbol; @@ -6268,7 +6348,10 @@ namespace ts { return type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) && ((type).objectFlags & ObjectFlags.Instantiated || isSymbolInScopeOfMappedTypeParameter(type.symbol, mapper)) ? - instantiateAnonymousType(type, mapper) : type; + instantiateAnonymousOrMappedType(type, mapper) : type; + } + if ((type).objectFlags & ObjectFlags.Mapped) { + return instantiateAnonymousOrMappedType(type, mapper); } if ((type).objectFlags & ObjectFlags.Reference) { return createTypeReference((type).target, instantiateList((type).typeArguments, mapper, instantiateType)); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 6858d028377..c721770d845 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2770,10 +2770,11 @@ namespace ts { Reference = 1 << 2, // Generic type reference Tuple = 1 << 3, // Synthesized generic tuple type Anonymous = 1 << 4, // Anonymous - Instantiated = 1 << 5, // Instantiated anonymous type - ObjectLiteral = 1 << 6, // Originates in an object literal - EvolvingArray = 1 << 7, // Evolving array type - ObjectLiteralPatternWithComputedProperties = 1 << 8, // Object literal pattern with computed properties + Mapped = 1 << 5, // Mapped + Instantiated = 1 << 6, // Instantiated anonymous or mapped type + ObjectLiteral = 1 << 7, // Originates in an object literal + EvolvingArray = 1 << 8, // Evolving array type + ObjectLiteralPatternWithComputedProperties = 1 << 9, // Object literal pattern with computed properties ClassOrInterface = Class | Interface } @@ -2842,6 +2843,15 @@ namespace ts { mapper?: TypeMapper; // Instantiation mapper } + /* @internal */ + export interface MappedType extends ObjectType { + declaration: MappedTypeNode; + typeParameter: TypeParameter; + templateType: Type; + target?: MappedType; // Instantiation target + mapper?: TypeMapper; // Instantiation mapper + } + export interface EvolvingArrayType extends ObjectType { elementType: Type; // Element expressions of evolving array type finalArrayType?: Type; // Final array type of evolving array type