Infer between generic mapped types before inferring from apparent type (#56640)

This commit is contained in:
Gabriela Araujo Britto
2024-01-02 15:35:10 -08:00
committed by GitHub
parent 9e0e9d35b9
commit be20dbbbbb
8 changed files with 216 additions and 7 deletions

View File

@@ -25544,6 +25544,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
else {
source = getReducedType(source);
if (isGenericMappedType(source) && isGenericMappedType(target)) {
invokeOnce(source, target, inferFromGenericMappedTypes);
}
if (!(priority & InferencePriority.NoConstraints && source.flags & (TypeFlags.Intersection | TypeFlags.Instantiable))) {
const apparentSource = getApparentType(source);
// getApparentType can return _any_ type, since an indexed access or conditional may simplify to any other type.
@@ -25581,6 +25584,19 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
priority = savePriority;
}
// Ensure an inference action is performed only once for the given source and target types.
// This includes two things:
// Avoiding inferring between the same pair of source and target types,
// and avoiding circularly inferring between source and target types.
// For an example of the last, consider if we are inferring between source type
// `type Deep<T> = { next: Deep<Deep<T>> }` and target type `type Loop<U> = { next: Loop<U> }`.
// We would then infer between the types of the `next` property: `Deep<Deep<T>>` = `{ next: Deep<Deep<Deep<T>>> }` and `Loop<U>` = `{ next: Loop<U> }`.
// We will then infer again between the types of the `next` property:
// `Deep<Deep<Deep<T>>>` and `Loop<U>`, and so on, such that we would be forever inferring
// between instantiations of the same types `Deep` and `Loop`.
// In particular, we would be inferring from increasingly deep instantiations of `Deep` to `Loop`,
// such that we would go on inferring forever, even though we would never infer
// between the same pair of types.
function invokeOnce<Source extends Type, Target extends Type>(source: Source, target: Target, action: (source: Source, target: Target) => void) {
const key = source.id + "," + target.id;
const status = visited && visited.get(key);
@@ -25888,6 +25904,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}
function inferFromGenericMappedTypes(source: MappedType, target: MappedType) {
// The source and target types are generic types { [P in S]: X } and { [P in T]: Y }, so we infer
// from S to T and from X to Y.
inferFromTypes(getConstraintTypeFromMappedType(source), getConstraintTypeFromMappedType(target));
inferFromTypes(getTemplateTypeFromMappedType(source), getTemplateTypeFromMappedType(target));
const sourceNameType = getNameTypeFromMappedType(source);
const targetNameType = getNameTypeFromMappedType(target);
if (sourceNameType && targetNameType) inferFromTypes(sourceNameType, targetNameType);
}
function inferFromObjectTypes(source: Type, target: Type) {
if (
getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (
@@ -25899,13 +25925,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return;
}
if (isGenericMappedType(source) && isGenericMappedType(target)) {
// The source and target types are generic types { [P in S]: X } and { [P in T]: Y }, so we infer
// from S to T and from X to Y.
inferFromTypes(getConstraintTypeFromMappedType(source), getConstraintTypeFromMappedType(target));
inferFromTypes(getTemplateTypeFromMappedType(source), getTemplateTypeFromMappedType(target));
const sourceNameType = getNameTypeFromMappedType(source);
const targetNameType = getNameTypeFromMappedType(target);
if (sourceNameType && targetNameType) inferFromTypes(sourceNameType, targetNameType);
inferFromGenericMappedTypes(source, target);
}
if (getObjectFlags(target) & ObjectFlags.Mapped && !(target as MappedType).declaration.nameType) {
const constraintType = getConstraintTypeFromMappedType(target as MappedType);