From 7d1a980ad2db5a3be8fe1cabf5898e5bebf9d6f9 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 13 Dec 2017 16:29:36 -0800 Subject: [PATCH] First attempt. Basically broken. --- src/compiler/checker.ts | 85 ++++++++++++++++++++++------------------- src/compiler/types.ts | 6 +++ 2 files changed, 51 insertions(+), 40 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c1b57e5def7..d333424dcb3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -334,6 +334,7 @@ namespace ts { const jsObjectLiteralIndexInfo = createIndexInfo(anyType, /*isReadonly*/ false); const globals = createSymbolTable(); + const deferredInferenceCache = createMap(); let ambientModulesCache: Symbol[] | undefined; /** * List of every ambient module with a "*" wildcard. @@ -4867,6 +4868,9 @@ namespace ts { if (getCheckFlags(symbol) & CheckFlags.Instantiated) { return getTypeOfInstantiatedSymbol(symbol); } + if (getCheckFlags(symbol) & CheckFlags.DeferredInferred) { + return inferTargetType((symbol as DeferredTransientSymbol).propertyType, (symbol as DeferredTransientSymbol).mappedType); + } if (symbol.flags & (SymbolFlags.Variable | SymbolFlags.Property)) { return getTypeOfVariableOrParameterOrProperty(symbol); } @@ -11224,42 +11228,49 @@ namespace ts { * property is computed by inferring from the source property type to X for the type * variable T[P] (i.e. we treat the type T[P] as the type variable we're inferring for). */ - function inferTypeForHomomorphicMappedType(source: Type, target: MappedType, mappedTypeStack: string[]): Type { - const properties = getPropertiesOfType(source); - let indexInfo = getIndexInfoOfType(source, IndexKind.String); - if (properties.length === 0 && !indexInfo) { - return undefined; + function inferTypeForHomomorphicMappedType(source: Type, target: MappedType): Type { + const key = source.id + "," + target.id; + if (deferredInferenceCache.has(key)) { + return deferredInferenceCache.get(key); } - const typeParameter = getIndexedAccessType((getConstraintTypeFromMappedType(target)).type, getTypeParameterFromMappedType(target)); - const inference = createInferenceInfo(typeParameter); - const inferences = [inference]; - const templateType = getTemplateTypeFromMappedType(target); - 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) { + deferredInferenceCache.set(key, function() { + const properties = getPropertiesOfType(source); + let indexInfo = getIndexInfoOfType(source, IndexKind.String); + if (properties.length === 0 && !indexInfo) { return undefined; } - const checkFlags = readonlyMask && isReadonlySymbol(prop) ? CheckFlags.Readonly : 0; - const inferredProp = createSymbol(SymbolFlags.Property | prop.flags & optionalMask, prop.escapedName, checkFlags); - inferredProp.declarations = prop.declarations; - inferredProp.type = inferTargetType(propType); - members.set(prop.escapedName, inferredProp); - } - if (indexInfo) { - indexInfo = createIndexInfo(inferTargetType(indexInfo.type), readonlyMask && indexInfo.isReadonly); - } - return createAnonymousType(undefined, members, emptyArray, emptyArray, indexInfo, 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.DeferredInferred | (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; // not sure I need this. + inferredProp.mappedType = target; + members.set(prop.escapedName, inferredProp); + } + if (indexInfo) { + // TODO: Defer this too. BARREL OF LAUGHS RIGHT THERE + indexInfo = createIndexInfo(inferTargetType(indexInfo.type, target), readonlyMask && indexInfo.isReadonly); + } + return createAnonymousType(undefined, members, emptyArray, emptyArray, indexInfo, undefined); + }()); + } - function inferTargetType(sourceType: Type): Type { - inference.candidates = undefined; - inferTypes(inferences, sourceType, templateType, 0, mappedTypeStack); - return inference.candidates ? getUnionType(inference.candidates, UnionReduction.Subtype) : emptyObjectType; - } + function inferTargetType(sourceType: Type, target: MappedType): Type { + const typeParameter = getIndexedAccessType((getConstraintTypeFromMappedType(target)).type, getTypeParameterFromMappedType(target)); + const templateType = getTemplateTypeFromMappedType(target); + const inference = createInferenceInfo(typeParameter); + inference.candidates = undefined; + inferTypes([inference], sourceType, templateType); + return inference.candidates ? getUnionType(inference.candidates, UnionReduction.Subtype) : emptyObjectType; } function getUnmatchedProperty(source: Type, target: Type, requireOptionalProperties: boolean) { @@ -11275,7 +11286,7 @@ namespace ts { return undefined; } - function inferTypes(inferences: InferenceInfo[], originalSource: Type, originalTarget: Type, priority: InferencePriority = 0, mappedTypeStack?: string[]) { + function inferTypes(inferences: InferenceInfo[], originalSource: Type, originalTarget: Type, priority: InferencePriority = 0) { let symbolStack: Symbol[]; let visited: Map; inferFromTypes(originalSource, originalTarget); @@ -11492,13 +11503,7 @@ namespace ts { // such that direct inferences to T get priority over inferences to Partial, for example. const inference = getInferenceInfoForType((constraintType).type); if (inference && !inference.isFixed) { - const key = (source.symbol ? getSymbolId(source.symbol) + "," : "") + getSymbolId(target.symbol); - if (contains(mappedTypeStack, key)) { - return; - } - (mappedTypeStack || (mappedTypeStack = [])).push(key); - const inferredType = inferTypeForHomomorphicMappedType(source, target, mappedTypeStack); - mappedTypeStack.pop(); + const inferredType = inferTypeForHomomorphicMappedType(source, target); if (inferredType) { const savePriority = priority; priority |= InferencePriority.MappedType; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 54f2ebfae42..16f546cecb2 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3217,6 +3217,7 @@ namespace ts { ContainsPrivate = 1 << 8, // Synthetic property with private constituent(s) ContainsStatic = 1 << 9, // Synthetic property with static constituent(s) Late = 1 << 10, // Late-bound symbol for a computed property with a dynamic name + DeferredInferred = 1 << 11, // Deferred inferred property of homomorphic mapped type. It is HILARIOUS. Synthetic = SyntheticProperty | SyntheticMethod } @@ -3226,6 +3227,11 @@ namespace ts { isRestParameter?: boolean; } + export interface DeferredTransientSymbol extends TransientSymbol { + propertyType: Type; + mappedType: MappedType; + } + export const enum InternalSymbolName { Call = "__call", // Call signatures Constructor = "__constructor", // Constructor implementations