From 35244230d5ac8cbf02eaa7063d8f9dd4e9d6ef1f Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 3 Jan 2018 14:20:14 -0800 Subject: [PATCH] Add deferred mapped types This allows index signature inference to be inferred as well. --- src/compiler/checker.ts | 62 +++++++++++++++++++++++------------------ src/compiler/types.ts | 8 ++++++ 2 files changed, 43 insertions(+), 27 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6e6bb0fb9a4..9a95a3300ca 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6114,6 +6114,23 @@ namespace ts { } } + function resolveDeferredMappedTypeMembers(type: DeferredMappedType) { + const indexInfo = type.targetIndexInfo; + const readonlyMask = type.mappedType.declaration.readonlyToken ? false : true; + const optionalMask = type.mappedType.declaration.questionToken ? 0 : SymbolFlags.Optional; + const stringIndexInfo = indexInfo && createIndexInfo(inferDeferredMappedType(indexInfo.type, type.mappedType), readonlyMask && indexInfo.isReadonly); + const members = createSymbolTable(); + for (const prop of type.sourceProperties) { + const checkFlags = CheckFlags.Deferred | (readonlyMask && isReadonlySymbol(prop) ? CheckFlags.Readonly : 0); + const inferredProp = createSymbol(SymbolFlags.Property | prop.flags & optionalMask, prop.escapedName, checkFlags) as DeferredTransientSymbol; + inferredProp.declarations = prop.declarations; + inferredProp.propertyType = getTypeOfSymbol(prop); + inferredProp.mappedType = type.mappedType; + members.set(prop.escapedName, inferredProp); + } + setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, undefined); + } + /** Resolve the members of a mapped type { [P in K]: T } */ function resolveMappedTypeMembers(type: MappedType) { const members: SymbolTable = createSymbolTable(); @@ -6253,6 +6270,9 @@ namespace ts { else if ((type).objectFlags & ObjectFlags.ClassOrInterface) { resolveClassOrInterfaceMembers(type); } + else if ((type).objectFlags & ObjectFlags.Deferred) { + resolveDeferredMappedTypeMembers(type as DeferredMappedType); + } else if ((type).objectFlags & ObjectFlags.Anonymous) { resolveAnonymousTypeMembers(type); } @@ -11291,35 +11311,23 @@ namespace ts { } function createDeferredMappedType(source: Type, target: MappedType) { - const properties = getPropertiesOfType(source); - let indexInfo = getIndexInfoOfType(source, IndexKind.String); - if (properties.length === 0 && !indexInfo) { + const properties = getPropertiesOfType(source); + let indexInfo = getIndexInfoOfType(source, IndexKind.String); + if (properties.length === 0 && !indexInfo) { + return undefined; + } + // If any property contains context sensitive functions that have been skipped, the source type + // is incomplete and we can't infer a meaningful input type. + for (const prop of properties) { + if (getTypeOfSymbol(prop).flags & TypeFlags.ContainsAnyFunctionType) { return undefined; } - const readonlyMask = target.declaration.readonlyToken ? false : true; - const optionalMask = target.declaration.questionToken ? 0 : SymbolFlags.Optional; - const members = createSymbolTable(); - for (const prop of properties) { - const propType = getTypeOfSymbol(prop); - // If any property contains context sensitive functions that have been skipped, the source type - // is incomplete and we can't infer a meaningful input type. - if (propType.flags & TypeFlags.ContainsAnyFunctionType) { - return undefined; - } - const checkFlags = CheckFlags.Deferred | (readonlyMask && isReadonlySymbol(prop) ? CheckFlags.Readonly : 0); - const inferredProp = createSymbol(SymbolFlags.Property | prop.flags & optionalMask, prop.escapedName, checkFlags) as DeferredTransientSymbol; - inferredProp.declarations = prop.declarations; - inferredProp.propertyType = propType; - inferredProp.mappedType = target; - members.set(prop.escapedName, inferredProp); - } - if (indexInfo) { - // TODO: Defer this too. - // (probably the simplest way is to have a special type that defers the creation of (at least) its index info in - // resolveStructuredTypeMembers - indexInfo = createIndexInfo(inferDeferredMappedType(indexInfo.type, target), readonlyMask && indexInfo.isReadonly); - } - return createAnonymousType(undefined, members, emptyArray, emptyArray, indexInfo, undefined); + } + const deferred = createObjectType(ObjectFlags.Deferred | ObjectFlags.Anonymous, undefined) as DeferredMappedType; + deferred.mappedType = target; + deferred.sourceProperties = properties; + deferred.targetIndexInfo = indexInfo; + return deferred; } function inferDeferredMappedType(sourceType: Type, target: MappedType): Type { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index b8ac1c67e8d..6c4b9c0658e 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3501,6 +3501,7 @@ namespace ts { EvolvingArray = 1 << 8, // Evolving array type ObjectLiteralPatternWithComputedProperties = 1 << 9, // Object literal pattern with computed properties ContainsSpread = 1 << 10, // Object literal contains spread operation + Deferred = 1 << 11, // Object contains a deferred inferred property ClassOrInterface = Class | Interface } @@ -3608,6 +3609,13 @@ namespace ts { finalArrayType?: Type; // Final array type of evolving array type } + /* @internal */ + export interface DeferredMappedType extends ObjectType { + targetIndexInfo?: IndexInfo; + mappedType: MappedType; + sourceProperties: Symbol[]; + } + /* @internal */ // Resolved object, union, or intersection type export interface ResolvedType extends ObjectType, UnionOrIntersectionType {