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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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);

View File

@ -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 = <T>(target: { [K in keyof T]: T[K] }) => void;
type bar = <U extends string[]>(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'.

View File

@ -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 = <T>(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 = <U extends string[]>(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))

View File

@ -0,0 +1,30 @@
//// [tests/cases/compiler/mappedTypeInferenceFromApparentType.ts] ////
=== mappedTypeInferenceFromApparentType.ts ===
type Obj = {
>Obj : { [s: string]: number; }
[s: string]: number;
>s : string
};
type foo = <T>(target: { [K in keyof T]: T[K] }) => void;
>foo : <T>(target: { [K in keyof T]: T[K]; }) => void
>target : { [K in keyof T]: T[K]; }
type bar = <U extends string[]>(source: { [K in keyof U]: Obj[K] }) => void;
>bar : <U extends string[]>(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

View File

@ -0,0 +1,42 @@
//// [tests/cases/compiler/mappedTypeInferenceToMappedType.ts] ////
=== mappedTypeInferenceToMappedType.ts ===
// #56133
declare class Base<T> {
>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<U extends unknown[]>(x: { [K in keyof U]: U[K] }): Base<U>;
>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<T> extends Base<T> {
>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<V extends unknown[]>(x: { [K in keyof V]: V[K] }): Base<V>;
>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))
}

View File

@ -0,0 +1,24 @@
//// [tests/cases/compiler/mappedTypeInferenceToMappedType.ts] ////
=== mappedTypeInferenceToMappedType.ts ===
// #56133
declare class Base<T> {
>Base : Base<T>
someProp: T;
>someProp : T
method<U extends unknown[]>(x: { [K in keyof U]: U[K] }): Base<U>;
>method : <U extends unknown[]>(x: { [K in keyof U]: U[K]; }) => Base<U>
>x : { [K in keyof U]: U[K]; }
}
declare class Derived<T> extends Base<T> {
>Derived : Derived<T>
>Base : Base<T>
method<V extends unknown[]>(x: { [K in keyof V]: V[K] }): Base<V>;
>method : <V extends unknown[]>(x: { [K in keyof V]: V[K]; }) => Base<V>
>x : { [K in keyof V]: V[K]; }
}

View File

@ -0,0 +1,13 @@
// @strict: true
// @noEmit: true
type Obj = {
[s: string]: number;
};
type foo = <T>(target: { [K in keyof T]: T[K] }) => void;
type bar = <U extends string[]>(source: { [K in keyof U]: Obj[K] }) => void;
declare let f: foo;
declare let b: bar;
b = f;

View File

@ -0,0 +1,13 @@
// @strict: true
// @noEmit: true
// #56133
declare class Base<T> {
someProp: T;
method<U extends unknown[]>(x: { [K in keyof U]: U[K] }): Base<U>;
}
declare class Derived<T> extends Base<T> {
method<V extends unknown[]>(x: { [K in keyof V]: V[K] }): Base<V>;
}