diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4cd242f264b1a..e1f7b267ba50b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -25257,6 +25257,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. @@ -25294,6 +25297,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 = { next: Deep> }` and target type `type Loop = { next: Loop }`. + // We would then infer between the types of the `next` property: `Deep>` = `{ next: Deep>> }` and `Loop` = `{ next: Loop }`. + // We will then infer again between the types of the `next` property: + // `Deep>>` and `Loop`, 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: Source, target: Target, action: (source: Source, target: Target) => void) { const key = source.id + "," + target.id; const status = visited && visited.get(key); @@ -25601,6 +25617,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 && ( @@ -25612,13 +25638,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); diff --git a/tests/baselines/reference/mappedTypeInferenceFromApparentType.errors.txt b/tests/baselines/reference/mappedTypeInferenceFromApparentType.errors.txt new file mode 100644 index 0000000000000..f61f0d58bcb99 --- /dev/null +++ b/tests/baselines/reference/mappedTypeInferenceFromApparentType.errors.txt @@ -0,0 +1,26 @@ +mappedTypeInferenceFromApparentType.ts(10,1): error TS2322: Type 'foo' is not assignable to type 'bar'. + Types of parameters 'target' and 'source' are incompatible. + Type '{ [K in keyof U]: Obj[K]; }' is not assignable to type '{ [K in keyof U]: U[K]; }'. + Type 'Obj[K]' is not assignable to type 'U[K]'. + Type 'Obj' is not assignable to type 'U'. + 'U' could be instantiated with an arbitrary type which could be unrelated to 'Obj'. + + +==== mappedTypeInferenceFromApparentType.ts (1 errors) ==== + type Obj = { + [s: string]: number; + }; + + type foo = (target: { [K in keyof T]: T[K] }) => void; + type bar = (source: { [K in keyof U]: Obj[K] }) => void; + + declare let f: foo; + declare let b: bar; + b = f; + ~ +!!! error TS2322: Type 'foo' is not assignable to type 'bar'. +!!! error TS2322: Types of parameters 'target' and 'source' are incompatible. +!!! error TS2322: Type '{ [K in keyof U]: Obj[K]; }' is not assignable to type '{ [K in keyof U]: U[K]; }'. +!!! error TS2322: Type 'Obj[K]' is not assignable to type 'U[K]'. +!!! error TS2322: Type 'Obj' is not assignable to type 'U'. +!!! error TS2322: 'U' could be instantiated with an arbitrary type which could be unrelated to 'Obj'. \ No newline at end of file diff --git a/tests/baselines/reference/mappedTypeInferenceFromApparentType.symbols b/tests/baselines/reference/mappedTypeInferenceFromApparentType.symbols new file mode 100644 index 0000000000000..3f76c44db9d59 --- /dev/null +++ b/tests/baselines/reference/mappedTypeInferenceFromApparentType.symbols @@ -0,0 +1,41 @@ +//// [tests/cases/compiler/mappedTypeInferenceFromApparentType.ts] //// + +=== mappedTypeInferenceFromApparentType.ts === +type Obj = { +>Obj : Symbol(Obj, Decl(mappedTypeInferenceFromApparentType.ts, 0, 0)) + + [s: string]: number; +>s : Symbol(s, Decl(mappedTypeInferenceFromApparentType.ts, 1, 5)) + +}; + +type foo = (target: { [K in keyof T]: T[K] }) => void; +>foo : Symbol(foo, Decl(mappedTypeInferenceFromApparentType.ts, 2, 2)) +>T : Symbol(T, Decl(mappedTypeInferenceFromApparentType.ts, 4, 12)) +>target : Symbol(target, Decl(mappedTypeInferenceFromApparentType.ts, 4, 15)) +>K : Symbol(K, Decl(mappedTypeInferenceFromApparentType.ts, 4, 26)) +>T : Symbol(T, Decl(mappedTypeInferenceFromApparentType.ts, 4, 12)) +>T : Symbol(T, Decl(mappedTypeInferenceFromApparentType.ts, 4, 12)) +>K : Symbol(K, Decl(mappedTypeInferenceFromApparentType.ts, 4, 26)) + +type bar = (source: { [K in keyof U]: Obj[K] }) => void; +>bar : Symbol(bar, Decl(mappedTypeInferenceFromApparentType.ts, 4, 57)) +>U : Symbol(U, Decl(mappedTypeInferenceFromApparentType.ts, 5, 12)) +>source : Symbol(source, Decl(mappedTypeInferenceFromApparentType.ts, 5, 32)) +>K : Symbol(K, Decl(mappedTypeInferenceFromApparentType.ts, 5, 43)) +>U : Symbol(U, Decl(mappedTypeInferenceFromApparentType.ts, 5, 12)) +>Obj : Symbol(Obj, Decl(mappedTypeInferenceFromApparentType.ts, 0, 0)) +>K : Symbol(K, Decl(mappedTypeInferenceFromApparentType.ts, 5, 43)) + +declare let f: foo; +>f : Symbol(f, Decl(mappedTypeInferenceFromApparentType.ts, 7, 11)) +>foo : Symbol(foo, Decl(mappedTypeInferenceFromApparentType.ts, 2, 2)) + +declare let b: bar; +>b : Symbol(b, Decl(mappedTypeInferenceFromApparentType.ts, 8, 11)) +>bar : Symbol(bar, Decl(mappedTypeInferenceFromApparentType.ts, 4, 57)) + +b = f; +>b : Symbol(b, Decl(mappedTypeInferenceFromApparentType.ts, 8, 11)) +>f : Symbol(f, Decl(mappedTypeInferenceFromApparentType.ts, 7, 11)) + diff --git a/tests/baselines/reference/mappedTypeInferenceFromApparentType.types b/tests/baselines/reference/mappedTypeInferenceFromApparentType.types new file mode 100644 index 0000000000000..41705b518d68e --- /dev/null +++ b/tests/baselines/reference/mappedTypeInferenceFromApparentType.types @@ -0,0 +1,30 @@ +//// [tests/cases/compiler/mappedTypeInferenceFromApparentType.ts] //// + +=== mappedTypeInferenceFromApparentType.ts === +type Obj = { +>Obj : { [s: string]: number; } + + [s: string]: number; +>s : string + +}; + +type foo = (target: { [K in keyof T]: T[K] }) => void; +>foo : (target: { [K in keyof T]: T[K]; }) => void +>target : { [K in keyof T]: T[K]; } + +type bar = (source: { [K in keyof U]: Obj[K] }) => void; +>bar : (source: { [K in keyof U]: Obj[K]; }) => void +>source : { [K in keyof U]: Obj[K]; } + +declare let f: foo; +>f : foo + +declare let b: bar; +>b : bar + +b = f; +>b = f : foo +>b : bar +>f : foo + diff --git a/tests/baselines/reference/mappedTypeInferenceToMappedType.symbols b/tests/baselines/reference/mappedTypeInferenceToMappedType.symbols new file mode 100644 index 0000000000000..b1f19eadcb01f --- /dev/null +++ b/tests/baselines/reference/mappedTypeInferenceToMappedType.symbols @@ -0,0 +1,42 @@ +//// [tests/cases/compiler/mappedTypeInferenceToMappedType.ts] //// + +=== mappedTypeInferenceToMappedType.ts === +// #56133 + +declare class Base { +>Base : Symbol(Base, Decl(mappedTypeInferenceToMappedType.ts, 0, 0)) +>T : Symbol(T, Decl(mappedTypeInferenceToMappedType.ts, 2, 19)) + + someProp: T; +>someProp : Symbol(Base.someProp, Decl(mappedTypeInferenceToMappedType.ts, 2, 23)) +>T : Symbol(T, Decl(mappedTypeInferenceToMappedType.ts, 2, 19)) + + method(x: { [K in keyof U]: U[K] }): Base; +>method : Symbol(Base.method, Decl(mappedTypeInferenceToMappedType.ts, 3, 16)) +>U : Symbol(U, Decl(mappedTypeInferenceToMappedType.ts, 4, 11)) +>x : Symbol(x, Decl(mappedTypeInferenceToMappedType.ts, 4, 32)) +>K : Symbol(K, Decl(mappedTypeInferenceToMappedType.ts, 4, 38)) +>U : Symbol(U, Decl(mappedTypeInferenceToMappedType.ts, 4, 11)) +>U : Symbol(U, Decl(mappedTypeInferenceToMappedType.ts, 4, 11)) +>K : Symbol(K, Decl(mappedTypeInferenceToMappedType.ts, 4, 38)) +>Base : Symbol(Base, Decl(mappedTypeInferenceToMappedType.ts, 0, 0)) +>U : Symbol(U, Decl(mappedTypeInferenceToMappedType.ts, 4, 11)) +} + +declare class Derived extends Base { +>Derived : Symbol(Derived, Decl(mappedTypeInferenceToMappedType.ts, 5, 1)) +>T : Symbol(T, Decl(mappedTypeInferenceToMappedType.ts, 7, 22)) +>Base : Symbol(Base, Decl(mappedTypeInferenceToMappedType.ts, 0, 0)) +>T : Symbol(T, Decl(mappedTypeInferenceToMappedType.ts, 7, 22)) + + method(x: { [K in keyof V]: V[K] }): Base; +>method : Symbol(Derived.method, Decl(mappedTypeInferenceToMappedType.ts, 7, 42)) +>V : Symbol(V, Decl(mappedTypeInferenceToMappedType.ts, 8, 11)) +>x : Symbol(x, Decl(mappedTypeInferenceToMappedType.ts, 8, 32)) +>K : Symbol(K, Decl(mappedTypeInferenceToMappedType.ts, 8, 38)) +>V : Symbol(V, Decl(mappedTypeInferenceToMappedType.ts, 8, 11)) +>V : Symbol(V, Decl(mappedTypeInferenceToMappedType.ts, 8, 11)) +>K : Symbol(K, Decl(mappedTypeInferenceToMappedType.ts, 8, 38)) +>Base : Symbol(Base, Decl(mappedTypeInferenceToMappedType.ts, 0, 0)) +>V : Symbol(V, Decl(mappedTypeInferenceToMappedType.ts, 8, 11)) +} diff --git a/tests/baselines/reference/mappedTypeInferenceToMappedType.types b/tests/baselines/reference/mappedTypeInferenceToMappedType.types new file mode 100644 index 0000000000000..9474e8de6fb98 --- /dev/null +++ b/tests/baselines/reference/mappedTypeInferenceToMappedType.types @@ -0,0 +1,24 @@ +//// [tests/cases/compiler/mappedTypeInferenceToMappedType.ts] //// + +=== mappedTypeInferenceToMappedType.ts === +// #56133 + +declare class Base { +>Base : Base + + someProp: T; +>someProp : T + + method(x: { [K in keyof U]: U[K] }): Base; +>method : (x: { [K in keyof U]: U[K]; }) => Base +>x : { [K in keyof U]: U[K]; } +} + +declare class Derived extends Base { +>Derived : Derived +>Base : Base + + method(x: { [K in keyof V]: V[K] }): Base; +>method : (x: { [K in keyof V]: V[K]; }) => Base +>x : { [K in keyof V]: V[K]; } +} diff --git a/tests/cases/compiler/mappedTypeInferenceFromApparentType.ts b/tests/cases/compiler/mappedTypeInferenceFromApparentType.ts new file mode 100644 index 0000000000000..9fc5e6401a72f --- /dev/null +++ b/tests/cases/compiler/mappedTypeInferenceFromApparentType.ts @@ -0,0 +1,13 @@ +// @strict: true +// @noEmit: true + +type Obj = { + [s: string]: number; +}; + +type foo = (target: { [K in keyof T]: T[K] }) => void; +type bar = (source: { [K in keyof U]: Obj[K] }) => void; + +declare let f: foo; +declare let b: bar; +b = f; \ No newline at end of file diff --git a/tests/cases/compiler/mappedTypeInferenceToMappedType.ts b/tests/cases/compiler/mappedTypeInferenceToMappedType.ts new file mode 100644 index 0000000000000..2bf82fac1c78a --- /dev/null +++ b/tests/cases/compiler/mappedTypeInferenceToMappedType.ts @@ -0,0 +1,13 @@ +// @strict: true +// @noEmit: true + +// #56133 + +declare class Base { + someProp: T; + method(x: { [K in keyof U]: U[K] }): Base; +} + +declare class Derived extends Base { + method(x: { [K in keyof V]: V[K] }): Base; +} \ No newline at end of file