diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2675f06824744..978970927313f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10349,8 +10349,16 @@ namespace ts { return links.resolvedType; } - function getActualTypeVariable(type: Type) { - return type.flags & TypeFlags.Substitution ? (type).typeVariable : type; + function getActualTypeVariable(type: Type): Type { + if (type.flags & TypeFlags.Substitution) { + return (type).typeVariable; + } + if (type.flags & TypeFlags.IndexedAccess && ( + (type).objectType.flags & TypeFlags.Substitution || + (type).indexType.flags & TypeFlags.Substitution)) { + return getIndexedAccessType(getActualTypeVariable((type).objectType), getActualTypeVariable((type).indexType)); + } + return type; } /** @@ -15030,6 +15038,9 @@ namespace ts { target = removeTypesFromUnionOrIntersection(target, matchingTypes); } } + else if (target.flags & (TypeFlags.IndexedAccess | TypeFlags.Substitution)) { + target = getActualTypeVariable(target); + } if (target.flags & TypeFlags.TypeVariable) { // If target is a type parameter, make an inference, unless the source type contains // the anyFunctionType (the wildcard type that's used to avoid contextually typing functions). @@ -15091,9 +15102,6 @@ namespace ts { } } } - else if (target.flags & TypeFlags.Substitution) { - inferFromTypes(source, (target as SubstitutionType).typeVariable); - } if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (source).target === (target).target) { // If source and target are references to the same generic type, infer from type arguments const sourceTypes = (source).typeArguments || emptyArray; diff --git a/tests/baselines/reference/substitutionTypesInIndexedAccessTypes.js b/tests/baselines/reference/substitutionTypesInIndexedAccessTypes.js new file mode 100644 index 0000000000000..c8a8ad5f227e7 --- /dev/null +++ b/tests/baselines/reference/substitutionTypesInIndexedAccessTypes.js @@ -0,0 +1,30 @@ +//// [substitutionTypesInIndexedAccessTypes.ts] +// Repro from #31086 + +type UserArgs = { + select?: boolean +}; + +type Subset = { [key in keyof T]: key extends keyof U ? T[key] : never }; + +declare function withBoundary(args?: Subset): T; +declare function withoutBoundary(args?: T): T; + +const boundaryResult = withBoundary({ + select: true, +}); + +const withoutBoundaryResult = withoutBoundary({ + select: true, +}); + + +//// [substitutionTypesInIndexedAccessTypes.js] +"use strict"; +// Repro from #31086 +var boundaryResult = withBoundary({ + select: true +}); +var withoutBoundaryResult = withoutBoundary({ + select: true +}); diff --git a/tests/baselines/reference/substitutionTypesInIndexedAccessTypes.symbols b/tests/baselines/reference/substitutionTypesInIndexedAccessTypes.symbols new file mode 100644 index 0000000000000..a0633c2320ec1 --- /dev/null +++ b/tests/baselines/reference/substitutionTypesInIndexedAccessTypes.symbols @@ -0,0 +1,58 @@ +=== tests/cases/compiler/substitutionTypesInIndexedAccessTypes.ts === +// Repro from #31086 + +type UserArgs = { +>UserArgs : Symbol(UserArgs, Decl(substitutionTypesInIndexedAccessTypes.ts, 0, 0)) + + select?: boolean +>select : Symbol(select, Decl(substitutionTypesInIndexedAccessTypes.ts, 2, 17)) + +}; + +type Subset = { [key in keyof T]: key extends keyof U ? T[key] : never }; +>Subset : Symbol(Subset, Decl(substitutionTypesInIndexedAccessTypes.ts, 4, 2)) +>T : Symbol(T, Decl(substitutionTypesInIndexedAccessTypes.ts, 6, 12)) +>U : Symbol(U, Decl(substitutionTypesInIndexedAccessTypes.ts, 6, 14)) +>key : Symbol(key, Decl(substitutionTypesInIndexedAccessTypes.ts, 6, 23)) +>T : Symbol(T, Decl(substitutionTypesInIndexedAccessTypes.ts, 6, 12)) +>key : Symbol(key, Decl(substitutionTypesInIndexedAccessTypes.ts, 6, 23)) +>U : Symbol(U, Decl(substitutionTypesInIndexedAccessTypes.ts, 6, 14)) +>T : Symbol(T, Decl(substitutionTypesInIndexedAccessTypes.ts, 6, 12)) +>key : Symbol(key, Decl(substitutionTypesInIndexedAccessTypes.ts, 6, 23)) + +declare function withBoundary(args?: Subset): T; +>withBoundary : Symbol(withBoundary, Decl(substitutionTypesInIndexedAccessTypes.ts, 6, 79)) +>T : Symbol(T, Decl(substitutionTypesInIndexedAccessTypes.ts, 8, 30)) +>UserArgs : Symbol(UserArgs, Decl(substitutionTypesInIndexedAccessTypes.ts, 0, 0)) +>args : Symbol(args, Decl(substitutionTypesInIndexedAccessTypes.ts, 8, 50)) +>Subset : Symbol(Subset, Decl(substitutionTypesInIndexedAccessTypes.ts, 4, 2)) +>T : Symbol(T, Decl(substitutionTypesInIndexedAccessTypes.ts, 8, 30)) +>UserArgs : Symbol(UserArgs, Decl(substitutionTypesInIndexedAccessTypes.ts, 0, 0)) +>T : Symbol(T, Decl(substitutionTypesInIndexedAccessTypes.ts, 8, 30)) + +declare function withoutBoundary(args?: T): T; +>withoutBoundary : Symbol(withoutBoundary, Decl(substitutionTypesInIndexedAccessTypes.ts, 8, 81)) +>T : Symbol(T, Decl(substitutionTypesInIndexedAccessTypes.ts, 9, 33)) +>UserArgs : Symbol(UserArgs, Decl(substitutionTypesInIndexedAccessTypes.ts, 0, 0)) +>args : Symbol(args, Decl(substitutionTypesInIndexedAccessTypes.ts, 9, 53)) +>T : Symbol(T, Decl(substitutionTypesInIndexedAccessTypes.ts, 9, 33)) +>T : Symbol(T, Decl(substitutionTypesInIndexedAccessTypes.ts, 9, 33)) + +const boundaryResult = withBoundary({ +>boundaryResult : Symbol(boundaryResult, Decl(substitutionTypesInIndexedAccessTypes.ts, 11, 5)) +>withBoundary : Symbol(withBoundary, Decl(substitutionTypesInIndexedAccessTypes.ts, 6, 79)) + + select: true, +>select : Symbol(select, Decl(substitutionTypesInIndexedAccessTypes.ts, 11, 37)) + +}); + +const withoutBoundaryResult = withoutBoundary({ +>withoutBoundaryResult : Symbol(withoutBoundaryResult, Decl(substitutionTypesInIndexedAccessTypes.ts, 15, 5)) +>withoutBoundary : Symbol(withoutBoundary, Decl(substitutionTypesInIndexedAccessTypes.ts, 8, 81)) + + select: true, +>select : Symbol(select, Decl(substitutionTypesInIndexedAccessTypes.ts, 15, 47)) + +}); + diff --git a/tests/baselines/reference/substitutionTypesInIndexedAccessTypes.types b/tests/baselines/reference/substitutionTypesInIndexedAccessTypes.types new file mode 100644 index 0000000000000..df7e4af01e618 --- /dev/null +++ b/tests/baselines/reference/substitutionTypesInIndexedAccessTypes.types @@ -0,0 +1,46 @@ +=== tests/cases/compiler/substitutionTypesInIndexedAccessTypes.ts === +// Repro from #31086 + +type UserArgs = { +>UserArgs : UserArgs + + select?: boolean +>select : boolean | undefined + +}; + +type Subset = { [key in keyof T]: key extends keyof U ? T[key] : never }; +>Subset : Subset + +declare function withBoundary(args?: Subset): T; +>withBoundary : (args?: Subset | undefined) => T +>args : Subset | undefined + +declare function withoutBoundary(args?: T): T; +>withoutBoundary : (args?: T | undefined) => T +>args : T | undefined + +const boundaryResult = withBoundary({ +>boundaryResult : { select: true; } +>withBoundary({ select: true,}) : { select: true; } +>withBoundary : (args?: Subset | undefined) => T +>{ select: true,} : { select: true; } + + select: true, +>select : true +>true : true + +}); + +const withoutBoundaryResult = withoutBoundary({ +>withoutBoundaryResult : { select: true; } +>withoutBoundary({ select: true,}) : { select: true; } +>withoutBoundary : (args?: T | undefined) => T +>{ select: true,} : { select: true; } + + select: true, +>select : true +>true : true + +}); + diff --git a/tests/cases/compiler/substitutionTypesInIndexedAccessTypes.ts b/tests/cases/compiler/substitutionTypesInIndexedAccessTypes.ts new file mode 100644 index 0000000000000..4a89ea3759533 --- /dev/null +++ b/tests/cases/compiler/substitutionTypesInIndexedAccessTypes.ts @@ -0,0 +1,20 @@ +// @strict: true + +// Repro from #31086 + +type UserArgs = { + select?: boolean +}; + +type Subset = { [key in keyof T]: key extends keyof U ? T[key] : never }; + +declare function withBoundary(args?: Subset): T; +declare function withoutBoundary(args?: T): T; + +const boundaryResult = withBoundary({ + select: true, +}); + +const withoutBoundaryResult = withoutBoundary({ + select: true, +});