From 0759bc67a471ddd8c47dd652d836e96eb1be4819 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 27 Apr 2019 16:19:50 -0700 Subject: [PATCH 1/4] Fix inference to indexed access type containing substitution type --- src/compiler/checker.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8ee0a4c051996..970f020f5f9f1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14860,6 +14860,14 @@ namespace ts { target = removeTypesFromUnionOrIntersection(target, matchingTypes); } } + else if (target.flags & TypeFlags.Substitution) { + target = (target as SubstitutionType).typeVariable; + } + else if (target.flags & TypeFlags.IndexedAccess && ( + (target).objectType.flags & TypeFlags.Substitution || + (target).indexType.flags & TypeFlags.Substitution)) { + target = getIndexedAccessType(getActualTypeVariable((target).objectType), getActualTypeVariable((target).indexType)); + } 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). @@ -14921,9 +14929,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; From 4f38aa88c2eb917b92d0d4081f2752a3212e66f5 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 27 Apr 2019 16:22:11 -0700 Subject: [PATCH 2/4] Add regression test --- .../substitutionTypesInIndexedAccessTypes.ts | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 tests/cases/compiler/substitutionTypesInIndexedAccessTypes.ts 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, +}); From ed75e1d07e54ed42696a8a3b973014591f5e066b Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 27 Apr 2019 16:22:20 -0700 Subject: [PATCH 3/4] Accept new baselines --- .../substitutionTypesInIndexedAccessTypes.js | 30 ++++++++++ ...stitutionTypesInIndexedAccessTypes.symbols | 58 +++++++++++++++++++ ...ubstitutionTypesInIndexedAccessTypes.types | 46 +++++++++++++++ 3 files changed, 134 insertions(+) create mode 100644 tests/baselines/reference/substitutionTypesInIndexedAccessTypes.js create mode 100644 tests/baselines/reference/substitutionTypesInIndexedAccessTypes.symbols create mode 100644 tests/baselines/reference/substitutionTypesInIndexedAccessTypes.types 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 + +}); + From 1818218d59c911fb522499ed011efcfe1a121d7e Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 30 Apr 2019 09:23:52 -0700 Subject: [PATCH 4/4] Move substitution type elimination to getActualTypeVariable --- src/compiler/checker.ts | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 970f020f5f9f1..bca3ca1bf1f8a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10318,8 +10318,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; } /** @@ -14860,13 +14868,8 @@ namespace ts { target = removeTypesFromUnionOrIntersection(target, matchingTypes); } } - else if (target.flags & TypeFlags.Substitution) { - target = (target as SubstitutionType).typeVariable; - } - else if (target.flags & TypeFlags.IndexedAccess && ( - (target).objectType.flags & TypeFlags.Substitution || - (target).indexType.flags & TypeFlags.Substitution)) { - target = getIndexedAccessType(getActualTypeVariable((target).objectType), getActualTypeVariable((target).indexType)); + 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