From 05de0a7da392e5385192f7638adeb249d98e695b Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Thu, 14 Dec 2017 13:33:02 -0800 Subject: [PATCH] Get it working: 1. Actually return the cached result! 2. Unnest worker function. 3. Improve all the names. 4. Pre-set the cache to undefined to avoid loops. (Not sure this is needed, though.) 5. Make the new type internal to avoid baseline changes. 6. Cut off recursion in the printing of recursive deferred mapped types. Note that (6) required introducing a new stack that is exactly like mappedTypeStack. I think the cache may actually be needed here, not in the creation of the deferred type. --- src/compiler/checker.ts | 41 +++++++++++++++---- src/compiler/types.ts | 1 + .../mappedTypeRecursiveInference.types | 4 +- 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d333424dcb3..dbc12b1461a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -335,6 +335,7 @@ namespace ts { const globals = createSymbolTable(); const deferredInferenceCache = createMap(); + const deferredMappedTypeInstantiationStack: string[] = []; let ambientModulesCache: Symbol[] | undefined; /** * List of every ambient module with a "*" wildcard. @@ -2867,7 +2868,23 @@ namespace ts { } for (const propertySymbol of properties) { - const propertyType = getTypeOfSymbol(propertySymbol); + let propertyType: Type; + if (getCheckFlags(propertySymbol) & CheckFlags.DeferredInferred) { + const deferred = propertySymbol as DeferredTransientSymbol; + const key = deferred.propertyType.id + "," + (deferred.mappedType.symbol ? deferred.mappedType.symbol.id : ""); + // Temporary solution to recursive printing: zero out repeated types. + if (contains(deferredMappedTypeInstantiationStack, key)) { + // TODO: Could probably be an actual cache that returns {} when it contains undefined. + propertyType = emptyObjectType; + } + else { + deferredMappedTypeInstantiationStack.push(key) + propertyType = getTypeOfSymbol(propertySymbol); + } + } + else { + propertyType = getTypeOfSymbol(propertySymbol); + } const saveEnclosingDeclaration = context.enclosingDeclaration; context.enclosingDeclaration = undefined; const propertyName = symbolToName(propertySymbol, context, SymbolFlags.Value, /*expectsIdentifier*/ true); @@ -2894,6 +2911,9 @@ namespace ts { /*initializer*/ undefined); typeElements.push(propertySignature); } + if (getCheckFlags(propertySymbol) & CheckFlags.DeferredInferred) { + deferredMappedTypeInstantiationStack.pop(); + } } return typeElements.length ? typeElements : undefined; } @@ -4869,7 +4889,7 @@ namespace ts { return getTypeOfInstantiatedSymbol(symbol); } if (getCheckFlags(symbol) & CheckFlags.DeferredInferred) { - return inferTargetType((symbol as DeferredTransientSymbol).propertyType, (symbol as DeferredTransientSymbol).mappedType); + return inferDeferredMappedType((symbol as DeferredTransientSymbol).propertyType, (symbol as DeferredTransientSymbol).mappedType); } if (symbol.flags & (SymbolFlags.Variable | SymbolFlags.Property)) { return getTypeOfVariableOrParameterOrProperty(symbol); @@ -11233,7 +11253,13 @@ namespace ts { if (deferredInferenceCache.has(key)) { return deferredInferenceCache.get(key); } - deferredInferenceCache.set(key, function() { + deferredInferenceCache.set(key, undefined); + const type = createDeferredMappedType(source, target); + deferredInferenceCache.set(key, type); + return type; + } + + function createDeferredMappedType(source: Type, target: MappedType) { const properties = getPropertiesOfType(source); let indexInfo = getIndexInfoOfType(source, IndexKind.String); if (properties.length === 0 && !indexInfo) { @@ -11257,14 +11283,15 @@ namespace ts { 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); + // 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); - }()); } - function inferTargetType(sourceType: Type, target: MappedType): Type { + function inferDeferredMappedType(sourceType: Type, target: MappedType): Type { const typeParameter = getIndexedAccessType((getConstraintTypeFromMappedType(target)).type, getTypeParameterFromMappedType(target)); const templateType = getTemplateTypeFromMappedType(target); const inference = createInferenceInfo(typeParameter); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 16f546cecb2..dc8c6d71618 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3227,6 +3227,7 @@ namespace ts { isRestParameter?: boolean; } + /* @internal */ export interface DeferredTransientSymbol extends TransientSymbol { propertyType: Type; mappedType: MappedType; diff --git a/tests/baselines/reference/mappedTypeRecursiveInference.types b/tests/baselines/reference/mappedTypeRecursiveInference.types index 382135591d5..0da9029f379 100644 --- a/tests/baselines/reference/mappedTypeRecursiveInference.types +++ b/tests/baselines/reference/mappedTypeRecursiveInference.types @@ -26,8 +26,8 @@ declare function foo(deep: Deep): T; >T : T const out = foo(a); ->out : { a: {}; } ->foo(a) : { a: {}; } +>out : { a: { a: {}; }; } +>foo(a) : { a: { a: {}; }; } >foo : (deep: Deep) => T >a : A