Skip to content

Commit e78ac47

Browse files
authored
Merge pull request microsoft#20711 from Microsoft/defer-inference-for-recursive-mapped-types
Defer inference for homomorphic mapped types
2 parents c6e4373 + f8c8655 commit e78ac47

17 files changed

+434
-104
lines changed

src/compiler/checker.ts

Lines changed: 67 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,7 @@ namespace ts {
339339
const jsObjectLiteralIndexInfo = createIndexInfo(anyType, /*isReadonly*/ false);
340340

341341
const globals = createSymbolTable();
342+
const reverseMappedCache = createMap<Type | undefined>();
342343
let ambientModulesCache: Symbol[] | undefined;
343344
/**
344345
* List of every ambient module with a "*" wildcard.
@@ -2860,7 +2861,10 @@ namespace ts {
28602861
typeElements.push(<ConstructSignatureDeclaration>signatureToSignatureDeclarationHelper(signature, SyntaxKind.ConstructSignature, context));
28612862
}
28622863
if (resolvedType.stringIndexInfo) {
2863-
typeElements.push(indexInfoToIndexSignatureDeclarationHelper(resolvedType.stringIndexInfo, IndexKind.String, context));
2864+
const indexInfo = resolvedType.objectFlags & ObjectFlags.ReverseMapped ?
2865+
createIndexInfo(anyType, resolvedType.stringIndexInfo.isReadonly, resolvedType.stringIndexInfo.declaration) :
2866+
resolvedType.stringIndexInfo;
2867+
typeElements.push(indexInfoToIndexSignatureDeclarationHelper(indexInfo, IndexKind.String, context));
28642868
}
28652869
if (resolvedType.numberIndexInfo) {
28662870
typeElements.push(indexInfoToIndexSignatureDeclarationHelper(resolvedType.numberIndexInfo, IndexKind.Number, context));
@@ -2872,7 +2876,7 @@ namespace ts {
28722876
}
28732877

28742878
for (const propertySymbol of properties) {
2875-
const propertyType = getTypeOfSymbol(propertySymbol);
2879+
const propertyType = getCheckFlags(propertySymbol) & CheckFlags.ReverseMapped ? anyType : getTypeOfSymbol(propertySymbol);
28762880
const saveEnclosingDeclaration = context.enclosingDeclaration;
28772881
context.enclosingDeclaration = undefined;
28782882
const propertyName = symbolToName(propertySymbol, context, SymbolFlags.Value, /*expectsIdentifier*/ true);
@@ -3681,7 +3685,10 @@ namespace ts {
36813685
writePunctuation(writer, SyntaxKind.SemicolonToken);
36823686
writer.writeLine();
36833687
}
3684-
buildIndexSignatureDisplay(resolved.stringIndexInfo, writer, IndexKind.String, enclosingDeclaration, globalFlags, symbolStack);
3688+
const stringIndexInfo = resolved.objectFlags & ObjectFlags.ReverseMapped && resolved.stringIndexInfo ?
3689+
createIndexInfo(anyType, resolved.stringIndexInfo.isReadonly, resolved.stringIndexInfo.declaration) :
3690+
resolved.stringIndexInfo;
3691+
buildIndexSignatureDisplay(stringIndexInfo, writer, IndexKind.String, enclosingDeclaration, globalFlags, symbolStack);
36853692
buildIndexSignatureDisplay(resolved.numberIndexInfo, writer, IndexKind.Number, enclosingDeclaration, globalFlags, symbolStack);
36863693
for (const p of resolved.properties) {
36873694
if (globalFlags & TypeFormatFlags.WriteClassExpressionAsTypeLiteral) {
@@ -3692,7 +3699,7 @@ namespace ts {
36923699
writer.reportPrivateInBaseOfClassExpression(symbolName(p));
36933700
}
36943701
}
3695-
const t = getTypeOfSymbol(p);
3702+
const t = getCheckFlags(p) & CheckFlags.ReverseMapped ? anyType : getTypeOfSymbol(p);
36963703
if (p.flags & (SymbolFlags.Function | SymbolFlags.Method) && !getPropertiesOfObjectType(t).length) {
36973704
const signatures = getSignaturesOfType(t, SignatureKind.Call);
36983705
for (const signature of signatures) {
@@ -4900,6 +4907,9 @@ namespace ts {
49004907
if (getCheckFlags(symbol) & CheckFlags.Instantiated) {
49014908
return getTypeOfInstantiatedSymbol(symbol);
49024909
}
4910+
if (getCheckFlags(symbol) & CheckFlags.ReverseMapped) {
4911+
return getTypeOfReverseMappedSymbol(symbol as ReverseMappedSymbol);
4912+
}
49034913
if (symbol.flags & (SymbolFlags.Variable | SymbolFlags.Property)) {
49044914
return getTypeOfVariableOrParameterOrProperty(symbol);
49054915
}
@@ -6110,6 +6120,23 @@ namespace ts {
61106120
}
61116121
}
61126122

6123+
function resolveReverseMappedTypeMembers(type: ReverseMappedType) {
6124+
const indexInfo = getIndexInfoOfType(type.source, IndexKind.String);
6125+
const readonlyMask = type.mappedType.declaration.readonlyToken ? false : true;
6126+
const optionalMask = type.mappedType.declaration.questionToken ? 0 : SymbolFlags.Optional;
6127+
const stringIndexInfo = indexInfo && createIndexInfo(inferReverseMappedType(indexInfo.type, type.mappedType), readonlyMask && indexInfo.isReadonly);
6128+
const members = createSymbolTable();
6129+
for (const prop of getPropertiesOfType(type.source)) {
6130+
const checkFlags = CheckFlags.ReverseMapped | (readonlyMask && isReadonlySymbol(prop) ? CheckFlags.Readonly : 0);
6131+
const inferredProp = createSymbol(SymbolFlags.Property | prop.flags & optionalMask, prop.escapedName, checkFlags) as ReverseMappedSymbol;
6132+
inferredProp.declarations = prop.declarations;
6133+
inferredProp.propertyType = getTypeOfSymbol(prop);
6134+
inferredProp.mappedType = type.mappedType;
6135+
members.set(prop.escapedName, inferredProp);
6136+
}
6137+
setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, undefined);
6138+
}
6139+
61136140
/** Resolve the members of a mapped type { [P in K]: T } */
61146141
function resolveMappedTypeMembers(type: MappedType) {
61156142
const members: SymbolTable = createSymbolTable();
@@ -6249,6 +6276,9 @@ namespace ts {
62496276
else if ((<ObjectType>type).objectFlags & ObjectFlags.ClassOrInterface) {
62506277
resolveClassOrInterfaceMembers(<InterfaceType>type);
62516278
}
6279+
else if ((<ReverseMappedType>type).objectFlags & ObjectFlags.ReverseMapped) {
6280+
resolveReverseMappedTypeMembers(type as ReverseMappedType);
6281+
}
62526282
else if ((<ObjectType>type).objectFlags & ObjectFlags.Anonymous) {
62536283
resolveAnonymousTypeMembers(<AnonymousType>type);
62546284
}
@@ -11275,42 +11305,45 @@ namespace ts {
1127511305
* property is computed by inferring from the source property type to X for the type
1127611306
* variable T[P] (i.e. we treat the type T[P] as the type variable we're inferring for).
1127711307
*/
11278-
function inferTypeForHomomorphicMappedType(source: Type, target: MappedType, mappedTypeStack: string[]): Type {
11308+
function inferTypeForHomomorphicMappedType(source: Type, target: MappedType): Type {
11309+
const key = source.id + "," + target.id;
11310+
if (reverseMappedCache.has(key)) {
11311+
return reverseMappedCache.get(key);
11312+
}
11313+
reverseMappedCache.set(key, undefined);
11314+
const type = createReverseMappedType(source, target);
11315+
reverseMappedCache.set(key, type);
11316+
return type;
11317+
}
11318+
11319+
function createReverseMappedType(source: Type, target: MappedType) {
1127911320
const properties = getPropertiesOfType(source);
11280-
let indexInfo = getIndexInfoOfType(source, IndexKind.String);
11281-
if (properties.length === 0 && !indexInfo) {
11321+
if (properties.length === 0 && !getIndexInfoOfType(source, IndexKind.String)) {
1128211322
return undefined;
1128311323
}
11284-
const typeParameter = <TypeParameter>getIndexedAccessType((<IndexType>getConstraintTypeFromMappedType(target)).type, getTypeParameterFromMappedType(target));
11285-
const inference = createInferenceInfo(typeParameter);
11286-
const inferences = [inference];
11287-
const templateType = getTemplateTypeFromMappedType(target);
11288-
const readonlyMask = target.declaration.readonlyToken ? false : true;
11289-
const optionalMask = target.declaration.questionToken ? 0 : SymbolFlags.Optional;
11290-
const members = createSymbolTable();
11324+
// If any property contains context sensitive functions that have been skipped, the source type
11325+
// is incomplete and we can't infer a meaningful input type.
1129111326
for (const prop of properties) {
11292-
const propType = getTypeOfSymbol(prop);
11293-
// If any property contains context sensitive functions that have been skipped, the source type
11294-
// is incomplete and we can't infer a meaningful input type.
11295-
if (propType.flags & TypeFlags.ContainsAnyFunctionType) {
11327+
if (getTypeOfSymbol(prop).flags & TypeFlags.ContainsAnyFunctionType) {
1129611328
return undefined;
1129711329
}
11298-
const checkFlags = readonlyMask && isReadonlySymbol(prop) ? CheckFlags.Readonly : 0;
11299-
const inferredProp = createSymbol(SymbolFlags.Property | prop.flags & optionalMask, prop.escapedName, checkFlags);
11300-
inferredProp.declarations = prop.declarations;
11301-
inferredProp.type = inferTargetType(propType);
11302-
members.set(prop.escapedName, inferredProp);
1130311330
}
11304-
if (indexInfo) {
11305-
indexInfo = createIndexInfo(inferTargetType(indexInfo.type), readonlyMask && indexInfo.isReadonly);
11306-
}
11307-
return createAnonymousType(undefined, members, emptyArray, emptyArray, indexInfo, undefined);
11331+
const reversed = createObjectType(ObjectFlags.ReverseMapped | ObjectFlags.Anonymous, /*symbol*/ undefined) as ReverseMappedType;
11332+
reversed.source = source;
11333+
reversed.mappedType = target;
11334+
return reversed;
11335+
}
1130811336

11309-
function inferTargetType(sourceType: Type): Type {
11310-
inference.candidates = undefined;
11311-
inferTypes(inferences, sourceType, templateType, 0, mappedTypeStack);
11312-
return inference.candidates ? getUnionType(inference.candidates, UnionReduction.Subtype) : emptyObjectType;
11313-
}
11337+
function getTypeOfReverseMappedSymbol(symbol: ReverseMappedSymbol) {
11338+
return inferReverseMappedType(symbol.propertyType, symbol.mappedType);
11339+
}
11340+
11341+
function inferReverseMappedType(sourceType: Type, target: MappedType): Type {
11342+
const typeParameter = <TypeParameter>getIndexedAccessType((<IndexType>getConstraintTypeFromMappedType(target)).type, getTypeParameterFromMappedType(target));
11343+
const templateType = getTemplateTypeFromMappedType(target);
11344+
const inference = createInferenceInfo(typeParameter);
11345+
inferTypes([inference], sourceType, templateType);
11346+
return inference.candidates ? getUnionType(inference.candidates, UnionReduction.Subtype) : emptyObjectType;
1131411347
}
1131511348

1131611349
function getUnmatchedProperty(source: Type, target: Type, requireOptionalProperties: boolean) {
@@ -11326,7 +11359,7 @@ namespace ts {
1132611359
return undefined;
1132711360
}
1132811361

11329-
function inferTypes(inferences: InferenceInfo[], originalSource: Type, originalTarget: Type, priority: InferencePriority = 0, mappedTypeStack?: string[]) {
11362+
function inferTypes(inferences: InferenceInfo[], originalSource: Type, originalTarget: Type, priority: InferencePriority = 0) {
1133011363
let symbolStack: Symbol[];
1133111364
let visited: Map<boolean>;
1133211365
inferFromTypes(originalSource, originalTarget);
@@ -11543,13 +11576,7 @@ namespace ts {
1154311576
// such that direct inferences to T get priority over inferences to Partial<T>, for example.
1154411577
const inference = getInferenceInfoForType((<IndexType>constraintType).type);
1154511578
if (inference && !inference.isFixed) {
11546-
const key = (source.symbol ? getSymbolId(source.symbol) + "," : "") + getSymbolId(target.symbol);
11547-
if (contains(mappedTypeStack, key)) {
11548-
return;
11549-
}
11550-
(mappedTypeStack || (mappedTypeStack = [])).push(key);
11551-
const inferredType = inferTypeForHomomorphicMappedType(source, <MappedType>target, mappedTypeStack);
11552-
mappedTypeStack.pop();
11579+
const inferredType = inferTypeForHomomorphicMappedType(source, <MappedType>target);
1155311580
if (inferredType) {
1155411581
const savePriority = priority;
1155511582
priority |= InferencePriority.MappedType;

src/compiler/types.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3251,6 +3251,7 @@ namespace ts {
32513251
ContainsPrivate = 1 << 8, // Synthetic property with private constituent(s)
32523252
ContainsStatic = 1 << 9, // Synthetic property with static constituent(s)
32533253
Late = 1 << 10, // Late-bound symbol for a computed property with a dynamic name
3254+
ReverseMapped = 1 << 11, // property of reverse-inferred homomorphic mapped type.
32543255
Synthetic = SyntheticProperty | SyntheticMethod
32553256
}
32563257

@@ -3260,6 +3261,12 @@ namespace ts {
32603261
isRestParameter?: boolean;
32613262
}
32623263

3264+
/* @internal */
3265+
export interface ReverseMappedSymbol extends TransientSymbol {
3266+
propertyType: Type;
3267+
mappedType: MappedType;
3268+
}
3269+
32633270
export const enum InternalSymbolName {
32643271
Call = "__call", // Call signatures
32653272
Constructor = "__constructor", // Constructor implementations
@@ -3494,6 +3501,7 @@ namespace ts {
34943501
EvolvingArray = 1 << 8, // Evolving array type
34953502
ObjectLiteralPatternWithComputedProperties = 1 << 9, // Object literal pattern with computed properties
34963503
ContainsSpread = 1 << 10, // Object literal contains spread operation
3504+
ReverseMapped = 1 << 11, // Object contains a property from a reverse-mapped type
34973505
ClassOrInterface = Class | Interface
34983506
}
34993507

@@ -3601,6 +3609,12 @@ namespace ts {
36013609
finalArrayType?: Type; // Final array type of evolving array type
36023610
}
36033611

3612+
/* @internal */
3613+
export interface ReverseMappedType extends ObjectType {
3614+
source: Type;
3615+
mappedType: MappedType;
3616+
}
3617+
36043618
/* @internal */
36053619
// Resolved object, union, or intersection type
36063620
export interface ResolvedType extends ObjectType, UnionOrIntersectionType {

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2066,6 +2066,7 @@ declare namespace ts {
20662066
EvolvingArray = 256,
20672067
ObjectLiteralPatternWithComputedProperties = 512,
20682068
ContainsSpread = 1024,
2069+
ReverseMapped = 2048,
20692070
ClassOrInterface = 3,
20702071
}
20712072
interface ObjectType extends Type {

tests/baselines/reference/api/typescript.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2066,6 +2066,7 @@ declare namespace ts {
20662066
EvolvingArray = 256,
20672067
ObjectLiteralPatternWithComputedProperties = 512,
20682068
ContainsSpread = 1024,
2069+
ReverseMapped = 2048,
20692070
ClassOrInterface = 3,
20702071
}
20712072
interface ObjectType extends Type {

tests/baselines/reference/isomorphicMappedTypeInference.js

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -310,17 +310,11 @@ declare type Spec<T> = {
310310
*/
311311
declare function applySpec<T>(obj: Spec<T>): (...args: any[]) => T;
312312
declare var g1: (...args: any[]) => {
313-
sum: number;
314-
nested: {
315-
mul: string;
316-
};
313+
sum: any;
314+
nested: any;
317315
};
318316
declare var g2: (...args: any[]) => {
319-
foo: {
320-
bar: {
321-
baz: boolean;
322-
};
323-
};
317+
foo: any;
324318
};
325319
declare const foo: <T>(object: T, partial: Partial<T>) => T;
326320
declare let o: {

0 commit comments

Comments
 (0)