Skip to content

Commit 5dd4c9e

Browse files
authored
Merge pull request #12528 from Microsoft/mappedTypeInference
Type inference for isomorphic mapped types
2 parents 9cac35b + 9970606 commit 5dd4c9e

File tree

5 files changed

+1140
-25
lines changed

5 files changed

+1140
-25
lines changed

Diff for: src/compiler/checker.ts

+72-25
Original file line numberDiff line numberDiff line change
@@ -4494,7 +4494,6 @@ namespace ts {
44944494
function resolveMappedTypeMembers(type: MappedType) {
44954495
const members: SymbolTable = createMap<Symbol>();
44964496
let stringIndexInfo: IndexInfo;
4497-
let numberIndexInfo: IndexInfo;
44984497
// Resolve upfront such that recursive references see an empty object type.
44994498
setStructuredTypeMembers(type, emptySymbols, emptyArray, emptyArray, undefined, undefined);
45004499
// In { [P in K]: T }, we refer to P as the type parameter type, K as the constraint type,
@@ -4529,16 +4528,8 @@ namespace ts {
45294528
else if (t.flags & TypeFlags.String) {
45304529
stringIndexInfo = createIndexInfo(propType, isReadonly);
45314530
}
4532-
else if (t.flags & TypeFlags.Number) {
4533-
numberIndexInfo = createIndexInfo(propType, isReadonly);
4534-
}
45354531
});
4536-
// If we created both a string and a numeric string index signature, and if the two index
4537-
// signatures have identical types, discard the redundant numeric index signature.
4538-
if (stringIndexInfo && numberIndexInfo && isTypeIdenticalTo(stringIndexInfo.type, numberIndexInfo.type)) {
4539-
numberIndexInfo = undefined;
4540-
}
4541-
setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo);
4532+
setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, undefined);
45424533
}
45434534

45444535
function getTypeParameterFromMappedType(type: MappedType) {
@@ -8432,7 +8423,7 @@ namespace ts {
84328423
// results for union and intersection types for performance reasons.
84338424
function couldContainTypeParameters(type: Type): boolean {
84348425
const objectFlags = getObjectFlags(type);
8435-
return !!(type.flags & TypeFlags.TypeParameter ||
8426+
return !!(type.flags & (TypeFlags.TypeParameter | TypeFlags.IndexedAccess) ||
84368427
objectFlags & ObjectFlags.Reference && forEach((<TypeReference>type).typeArguments, couldContainTypeParameters) ||
84378428
objectFlags & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & (SymbolFlags.Method | SymbolFlags.TypeLiteral | SymbolFlags.Class) ||
84388429
objectFlags & ObjectFlags.Mapped ||
@@ -8450,8 +8441,57 @@ namespace ts {
84508441
return type === typeParameter || type.flags & TypeFlags.UnionOrIntersection && forEach((<UnionOrIntersectionType>type).types, t => isTypeParameterAtTopLevel(t, typeParameter));
84518442
}
84528443

8453-
function inferTypes(context: InferenceContext, originalSource: Type, originalTarget: Type) {
8454-
const typeParameters = context.signature.typeParameters;
8444+
// Infer a suitable input type for an isomorphic mapped type { [P in keyof T]: X }. We construct
8445+
// an object type with the same set of properties as the source type, where the type of each
8446+
// property is computed by inferring from the source property type to X for a synthetic type
8447+
// parameter T[P] (i.e. we treat the type T[P] as the type parameter we're inferring for).
8448+
function inferTypeForIsomorphicMappedType(source: Type, target: MappedType): Type {
8449+
if (!isMappableType(source)) {
8450+
return source;
8451+
}
8452+
const typeParameter = getIndexedAccessType((<IndexType>getConstraintTypeFromMappedType(target)).type, getTypeParameterFromMappedType(target));
8453+
const typeParameterArray = [typeParameter];
8454+
const typeInferences = createTypeInferencesObject();
8455+
const typeInferencesArray = [typeInferences];
8456+
const templateType = getTemplateTypeFromMappedType(target);
8457+
const properties = getPropertiesOfType(source);
8458+
const members = createSymbolTable(properties);
8459+
let hasInferredTypes = false;
8460+
for (const prop of properties) {
8461+
const inferredPropType = inferTargetType(getTypeOfSymbol(prop));
8462+
if (inferredPropType) {
8463+
const inferredProp = <TransientSymbol>createSymbol(SymbolFlags.Property | SymbolFlags.Transient | prop.flags & SymbolFlags.Optional, prop.name);
8464+
inferredProp.declarations = prop.declarations;
8465+
inferredProp.type = inferredPropType;
8466+
inferredProp.isReadonly = isReadonlySymbol(prop);
8467+
members[prop.name] = inferredProp;
8468+
hasInferredTypes = true;
8469+
}
8470+
}
8471+
let indexInfo = getIndexInfoOfType(source, IndexKind.String);
8472+
if (indexInfo) {
8473+
const inferredIndexType = inferTargetType(indexInfo.type);
8474+
if (inferredIndexType) {
8475+
indexInfo = createIndexInfo(inferredIndexType, indexInfo.isReadonly);
8476+
hasInferredTypes = true;
8477+
}
8478+
}
8479+
return hasInferredTypes ? createAnonymousType(undefined, members, emptyArray, emptyArray, indexInfo, undefined) : source;
8480+
8481+
function inferTargetType(sourceType: Type): Type {
8482+
typeInferences.primary = undefined;
8483+
typeInferences.secondary = undefined;
8484+
inferTypes(typeParameterArray, typeInferencesArray, sourceType, templateType);
8485+
const inferences = typeInferences.primary || typeInferences.secondary;
8486+
return inferences && getUnionType(inferences, /*subtypeReduction*/ true);
8487+
}
8488+
}
8489+
8490+
function inferTypesWithContext(context: InferenceContext, originalSource: Type, originalTarget: Type) {
8491+
inferTypes(context.signature.typeParameters, context.inferences, originalSource, originalTarget);
8492+
}
8493+
8494+
function inferTypes(typeParameters: Type[], typeInferences: TypeInferences[], originalSource: Type, originalTarget: Type) {
84558495
let sourceStack: Type[];
84568496
let targetStack: Type[];
84578497
let depth = 0;
@@ -8519,7 +8559,7 @@ namespace ts {
85198559
target = removeTypesFromUnionOrIntersection(<UnionOrIntersectionType>target, matchingTypes);
85208560
}
85218561
}
8522-
if (target.flags & TypeFlags.TypeParameter) {
8562+
if (target.flags & (TypeFlags.TypeParameter | TypeFlags.IndexedAccess)) {
85238563
// If target is a type parameter, make an inference, unless the source type contains
85248564
// the anyFunctionType (the wildcard type that's used to avoid contextually typing functions).
85258565
// Because the anyFunctionType is internal, it should not be exposed to the user by adding
@@ -8531,7 +8571,7 @@ namespace ts {
85318571
}
85328572
for (let i = 0; i < typeParameters.length; i++) {
85338573
if (target === typeParameters[i]) {
8534-
const inferences = context.inferences[i];
8574+
const inferences = typeInferences[i];
85358575
if (!inferences.isFixed) {
85368576
// Any inferences that are made to a type parameter in a union type are inferior
85378577
// to inferences made to a flat (non-union) type. This is because if we infer to
@@ -8545,7 +8585,7 @@ namespace ts {
85458585
if (!contains(candidates, source)) {
85468586
candidates.push(source);
85478587
}
8548-
if (!isTypeParameterAtTopLevel(originalTarget, <TypeParameter>target)) {
8588+
if (target.flags & TypeFlags.TypeParameter && !isTypeParameterAtTopLevel(originalTarget, <TypeParameter>target)) {
85498589
inferences.topLevel = false;
85508590
}
85518591
}
@@ -8622,12 +8662,19 @@ namespace ts {
86228662
function inferFromObjectTypes(source: Type, target: Type) {
86238663
if (getObjectFlags(target) & ObjectFlags.Mapped) {
86248664
const constraintType = getConstraintTypeFromMappedType(<MappedType>target);
8625-
if (getObjectFlags(source) & ObjectFlags.Mapped) {
8626-
inferFromTypes(getConstraintTypeFromMappedType(<MappedType>source), constraintType);
8627-
inferFromTypes(getTemplateTypeFromMappedType(<MappedType>source), getTemplateTypeFromMappedType(<MappedType>target));
8665+
if (constraintType.flags & TypeFlags.Index) {
8666+
// We're inferring from some source type S to an isomorphic mapped type { [P in keyof T]: X },
8667+
// where T is a type parameter. Use inferTypeForIsomorphicMappedType to infer a suitable source
8668+
// type and then infer from that type to T.
8669+
const index = indexOf(typeParameters, (<IndexType>constraintType).type);
8670+
if (index >= 0 && !typeInferences[index].isFixed) {
8671+
inferFromTypes(inferTypeForIsomorphicMappedType(source, <MappedType>target), typeParameters[index]);
8672+
}
86288673
return;
86298674
}
86308675
if (constraintType.flags & TypeFlags.TypeParameter) {
8676+
// We're inferring from some source type S to a mapped type { [P in T]: X }, where T is a type
8677+
// parameter. Infer from 'keyof S' to T and infer from a union of each property type in S to X.
86318678
inferFromTypes(getIndexType(source), constraintType);
86328679
inferFromTypes(getUnionType(map(getPropertiesOfType(source), getTypeOfSymbol)), getTemplateTypeFromMappedType(<MappedType>target));
86338680
return;
@@ -12469,7 +12516,7 @@ namespace ts {
1246912516
const context = createInferenceContext(signature, /*inferUnionTypes*/ true);
1247012517
forEachMatchingParameterType(contextualSignature, signature, (source, target) => {
1247112518
// Type parameters from outer context referenced by source type are fixed by instantiation of the source type
12472-
inferTypes(context, instantiateType(source, contextualMapper), target);
12519+
inferTypesWithContext(context, instantiateType(source, contextualMapper), target);
1247312520
});
1247412521
return getSignatureInstantiation(signature, getInferredTypes(context));
1247512522
}
@@ -12504,7 +12551,7 @@ namespace ts {
1250412551
if (thisType) {
1250512552
const thisArgumentNode = getThisArgumentOfCall(node);
1250612553
const thisArgumentType = thisArgumentNode ? checkExpression(thisArgumentNode) : voidType;
12507-
inferTypes(context, thisArgumentType, thisType);
12554+
inferTypesWithContext(context, thisArgumentType, thisType);
1250812555
}
1250912556

1251012557
// We perform two passes over the arguments. In the first pass we infer from all arguments, but use
@@ -12526,7 +12573,7 @@ namespace ts {
1252612573
argType = checkExpressionWithContextualType(arg, paramType, mapper);
1252712574
}
1252812575

12529-
inferTypes(context, argType, paramType);
12576+
inferTypesWithContext(context, argType, paramType);
1253012577
}
1253112578
}
1253212579

@@ -12541,7 +12588,7 @@ namespace ts {
1254112588
if (excludeArgument[i] === false) {
1254212589
const arg = args[i];
1254312590
const paramType = getTypeAtPosition(signature, i);
12544-
inferTypes(context, checkExpressionWithContextualType(arg, paramType, inferenceMapper), paramType);
12591+
inferTypesWithContext(context, checkExpressionWithContextualType(arg, paramType, inferenceMapper), paramType);
1254512592
}
1254612593
}
1254712594
}
@@ -13628,7 +13675,7 @@ namespace ts {
1362813675
for (let i = 0; i < len; i++) {
1362913676
const declaration = <ParameterDeclaration>signature.parameters[i].valueDeclaration;
1363013677
if (declaration.type) {
13631-
inferTypes(mapper.context, getTypeFromTypeNode(declaration.type), getTypeAtPosition(context, i));
13678+
inferTypesWithContext(mapper.context, getTypeFromTypeNode(declaration.type), getTypeAtPosition(context, i));
1363213679
}
1363313680
}
1363413681
}
@@ -13714,7 +13761,7 @@ namespace ts {
1371413761
// T in the second overload so that we do not infer Base as a candidate for T
1371513762
// (inferring Base would make type argument inference inconsistent between the two
1371613763
// overloads).
13717-
inferTypes(mapper.context, links.type, instantiateType(contextualType, mapper));
13764+
inferTypesWithContext(mapper.context, links.type, instantiateType(contextualType, mapper));
1371813765
}
1371913766
}
1372013767

0 commit comments

Comments
 (0)