Skip to content

Commit 36ad772

Browse files
committed
Type inference for isomorphic mapped types
1 parent e128add commit 36ad772

File tree

1 file changed

+75
-12
lines changed

1 file changed

+75
-12
lines changed

src/compiler/checker.ts

Lines changed: 75 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8425,7 +8425,7 @@ namespace ts {
84258425
// results for union and intersection types for performance reasons.
84268426
function couldContainTypeParameters(type: Type): boolean {
84278427
const objectFlags = getObjectFlags(type);
8428-
return !!(type.flags & TypeFlags.TypeParameter ||
8428+
return !!(type.flags & (TypeFlags.TypeParameter | TypeFlags.IndexedAccess) ||
84298429
objectFlags & ObjectFlags.Reference && forEach((<TypeReference>type).typeArguments, couldContainTypeParameters) ||
84308430
objectFlags & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & (SymbolFlags.Method | SymbolFlags.TypeLiteral | SymbolFlags.Class) ||
84318431
objectFlags & ObjectFlags.Mapped ||
@@ -8443,8 +8443,57 @@ namespace ts {
84438443
return type === typeParameter || type.flags & TypeFlags.UnionOrIntersection && forEach((<UnionOrIntersectionType>type).types, t => isTypeParameterAtTopLevel(t, typeParameter));
84448444
}
84458445

8446-
function inferTypes(context: InferenceContext, originalSource: Type, originalTarget: Type) {
8447-
const typeParameters = context.signature.typeParameters;
8446+
// Infer a suitable input type for an isomorphic mapped type { [P in keyof T]: X }. We construct
8447+
// an object type with the same set of properties as the source type, where the type of each
8448+
// property is computed by inferring from the source property type to X for a synthetic type
8449+
// parameter T[P] (i.e. we treat the type T[P] as the type parameter we're inferring for).
8450+
function inferTypeForIsomorphicMappedType(source: Type, target: MappedType): Type {
8451+
if (!isMappableType(source)) {
8452+
return source;
8453+
}
8454+
const typeParameter = getIndexedAccessType((<IndexType>getConstraintTypeFromMappedType(target)).type, getTypeParameterFromMappedType(target));
8455+
const typeParameterArray = [typeParameter];
8456+
const typeInferences = createTypeInferencesObject();
8457+
const typeInferencesArray = [typeInferences];
8458+
const templateType = getTemplateTypeFromMappedType(target);
8459+
const properties = getPropertiesOfType(source);
8460+
const members = createSymbolTable(properties);
8461+
let hasInferredTypes = false;
8462+
for (const prop of properties) {
8463+
const inferredPropType = inferTargetType(getTypeOfSymbol(prop));
8464+
if (inferredPropType) {
8465+
const inferredProp = <TransientSymbol>createSymbol(SymbolFlags.Property | SymbolFlags.Transient | prop.flags & SymbolFlags.Optional, prop.name);
8466+
inferredProp.declarations = prop.declarations;
8467+
inferredProp.type = inferredPropType;
8468+
inferredProp.isReadonly = isReadonlySymbol(prop);
8469+
members[prop.name] = inferredProp;
8470+
hasInferredTypes = true;
8471+
}
8472+
}
8473+
let indexInfo = getIndexInfoOfType(source, IndexKind.String);
8474+
if (indexInfo) {
8475+
const inferredIndexType = inferTargetType(indexInfo.type);
8476+
if (inferredIndexType) {
8477+
indexInfo = createIndexInfo(inferredIndexType, indexInfo.isReadonly);
8478+
hasInferredTypes = true;
8479+
}
8480+
}
8481+
return hasInferredTypes ? createAnonymousType(undefined, members, emptyArray, emptyArray, indexInfo, undefined) : source;
8482+
8483+
function inferTargetType(sourceType: Type): Type {
8484+
typeInferences.primary = undefined;
8485+
typeInferences.secondary = undefined;
8486+
inferTypes(typeParameterArray, typeInferencesArray, sourceType, templateType);
8487+
const inferences = typeInferences.primary || typeInferences.secondary;
8488+
return inferences && getUnionType(inferences, /*subtypeReduction*/ true);
8489+
}
8490+
}
8491+
8492+
function inferTypesWithContext(context: InferenceContext, originalSource: Type, originalTarget: Type) {
8493+
inferTypes(context.signature.typeParameters, context.inferences, originalSource, originalTarget);
8494+
}
8495+
8496+
function inferTypes(typeParameters: Type[], typeInferences: TypeInferences[], originalSource: Type, originalTarget: Type) {
84488497
let sourceStack: Type[];
84498498
let targetStack: Type[];
84508499
let depth = 0;
@@ -8512,7 +8561,7 @@ namespace ts {
85128561
target = removeTypesFromUnionOrIntersection(<UnionOrIntersectionType>target, matchingTypes);
85138562
}
85148563
}
8515-
if (target.flags & TypeFlags.TypeParameter) {
8564+
if (target.flags & (TypeFlags.TypeParameter | TypeFlags.IndexedAccess)) {
85168565
// If target is a type parameter, make an inference, unless the source type contains
85178566
// the anyFunctionType (the wildcard type that's used to avoid contextually typing functions).
85188567
// Because the anyFunctionType is internal, it should not be exposed to the user by adding
@@ -8524,7 +8573,7 @@ namespace ts {
85248573
}
85258574
for (let i = 0; i < typeParameters.length; i++) {
85268575
if (target === typeParameters[i]) {
8527-
const inferences = context.inferences[i];
8576+
const inferences = typeInferences[i];
85288577
if (!inferences.isFixed) {
85298578
// Any inferences that are made to a type parameter in a union type are inferior
85308579
// to inferences made to a flat (non-union) type. This is because if we infer to
@@ -8538,7 +8587,7 @@ namespace ts {
85388587
if (!contains(candidates, source)) {
85398588
candidates.push(source);
85408589
}
8541-
if (!isTypeParameterAtTopLevel(originalTarget, <TypeParameter>target)) {
8590+
if (target.flags & TypeFlags.TypeParameter && !isTypeParameterAtTopLevel(originalTarget, <TypeParameter>target)) {
85428591
inferences.topLevel = false;
85438592
}
85448593
}
@@ -8589,15 +8638,29 @@ namespace ts {
85898638
if (getObjectFlags(target) & ObjectFlags.Mapped) {
85908639
const constraintType = getConstraintTypeFromMappedType(<MappedType>target);
85918640
if (getObjectFlags(source) & ObjectFlags.Mapped) {
8641+
// We're inferring from a mapped type to a mapped type, so simply infer from constraint type to
8642+
// constraint type and from template type to template type.
85928643
inferFromTypes(getConstraintTypeFromMappedType(<MappedType>source), constraintType);
85938644
inferFromTypes(getTemplateTypeFromMappedType(<MappedType>source), getTemplateTypeFromMappedType(<MappedType>target));
85948645
return;
85958646
}
85968647
if (constraintType.flags & TypeFlags.TypeParameter) {
8648+
// We're inferring from some source type S to a mapped type { [P in T]: X }, where T is a type
8649+
// parameter. Infer from 'keyof S' to T and infer from a union of each property type in S to X.
85978650
inferFromTypes(getIndexType(source), constraintType);
85988651
inferFromTypes(getUnionType(map(getPropertiesOfType(source), getTypeOfSymbol)), getTemplateTypeFromMappedType(<MappedType>target));
85998652
return;
86008653
}
8654+
if (constraintType.flags & TypeFlags.Index) {
8655+
// We're inferring from some source type S to an isomorphic mapped type { [P in keyof T]: X },
8656+
// where T is a type parameter. Use inferTypeForIsomorphicMappedType to infer a suitable source
8657+
// type and then infer from that type to T.
8658+
const index = indexOf(typeParameters, (<IndexType>constraintType).type);
8659+
if (index >= 0 && !typeInferences[index].isFixed) {
8660+
inferFromTypes(inferTypeForIsomorphicMappedType(source, <MappedType>target), typeParameters[index]);
8661+
}
8662+
return;
8663+
}
86018664
}
86028665
source = getApparentType(source);
86038666
if (source.flags & TypeFlags.Object) {
@@ -12458,7 +12521,7 @@ namespace ts {
1245812521
const context = createInferenceContext(signature, /*inferUnionTypes*/ true);
1245912522
forEachMatchingParameterType(contextualSignature, signature, (source, target) => {
1246012523
// Type parameters from outer context referenced by source type are fixed by instantiation of the source type
12461-
inferTypes(context, instantiateType(source, contextualMapper), target);
12524+
inferTypesWithContext(context, instantiateType(source, contextualMapper), target);
1246212525
});
1246312526
return getSignatureInstantiation(signature, getInferredTypes(context));
1246412527
}
@@ -12493,7 +12556,7 @@ namespace ts {
1249312556
if (thisType) {
1249412557
const thisArgumentNode = getThisArgumentOfCall(node);
1249512558
const thisArgumentType = thisArgumentNode ? checkExpression(thisArgumentNode) : voidType;
12496-
inferTypes(context, thisArgumentType, thisType);
12559+
inferTypesWithContext(context, thisArgumentType, thisType);
1249712560
}
1249812561

1249912562
// We perform two passes over the arguments. In the first pass we infer from all arguments, but use
@@ -12515,7 +12578,7 @@ namespace ts {
1251512578
argType = checkExpressionWithContextualType(arg, paramType, mapper);
1251612579
}
1251712580

12518-
inferTypes(context, argType, paramType);
12581+
inferTypesWithContext(context, argType, paramType);
1251912582
}
1252012583
}
1252112584

@@ -12530,7 +12593,7 @@ namespace ts {
1253012593
if (excludeArgument[i] === false) {
1253112594
const arg = args[i];
1253212595
const paramType = getTypeAtPosition(signature, i);
12533-
inferTypes(context, checkExpressionWithContextualType(arg, paramType, inferenceMapper), paramType);
12596+
inferTypesWithContext(context, checkExpressionWithContextualType(arg, paramType, inferenceMapper), paramType);
1253412597
}
1253512598
}
1253612599
}
@@ -13617,7 +13680,7 @@ namespace ts {
1361713680
for (let i = 0; i < len; i++) {
1361813681
const declaration = <ParameterDeclaration>signature.parameters[i].valueDeclaration;
1361913682
if (declaration.type) {
13620-
inferTypes(mapper.context, getTypeFromTypeNode(declaration.type), getTypeAtPosition(context, i));
13683+
inferTypesWithContext(mapper.context, getTypeFromTypeNode(declaration.type), getTypeAtPosition(context, i));
1362113684
}
1362213685
}
1362313686
}
@@ -13703,7 +13766,7 @@ namespace ts {
1370313766
// T in the second overload so that we do not infer Base as a candidate for T
1370413767
// (inferring Base would make type argument inference inconsistent between the two
1370513768
// overloads).
13706-
inferTypes(mapper.context, links.type, instantiateType(contextualType, mapper));
13769+
inferTypesWithContext(mapper.context, links.type, instantiateType(contextualType, mapper));
1370713770
}
1370813771
}
1370913772

0 commit comments

Comments
 (0)