From 9da38722993bd92888032330b874d37f70c03b4f Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 27 Jun 2022 10:11:15 -1000 Subject: [PATCH 1/7] 'keyof undefined' and 'keyof null same as 'keyof never' --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a53c1841b6ef4..ea0988f84e804 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15255,7 +15255,7 @@ namespace ts { getObjectFlags(type) & ObjectFlags.Mapped ? getIndexTypeForMappedType(type as MappedType, stringsOnly, noIndexSignatures) : type === wildcardType ? wildcardType : type.flags & TypeFlags.Unknown ? neverType : - type.flags & (TypeFlags.Any | TypeFlags.Never) ? keyofConstraintType : + type.flags & (TypeFlags.Any | TypeFlags.Nullable | TypeFlags.Never) ? keyofConstraintType : getLiteralTypeFromProperties(type, (noIndexSignatures ? TypeFlags.StringLiteral : TypeFlags.StringLike) | (stringsOnly ? 0 : TypeFlags.NumberLike | TypeFlags.ESSymbolLike), stringsOnly === keyofStringsOnly && !noIndexSignatures); } From ab04c57243af424e141f0edf9302510947f06d27 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 27 Jun 2022 10:11:22 -1000 Subject: [PATCH 2/7] Update tests --- .../baselines/reference/keyofAndIndexedAccess.errors.txt | 4 ++-- tests/baselines/reference/keyofAndIndexedAccess.js | 4 ++-- tests/baselines/reference/keyofAndIndexedAccess.symbols | 4 ++-- tests/baselines/reference/keyofAndIndexedAccess.types | 8 ++++---- .../conformance/types/keyof/keyofAndIndexedAccess.ts | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/baselines/reference/keyofAndIndexedAccess.errors.txt b/tests/baselines/reference/keyofAndIndexedAccess.errors.txt index 8917c5e588822..40e14a8643937 100644 --- a/tests/baselines/reference/keyofAndIndexedAccess.errors.txt +++ b/tests/baselines/reference/keyofAndIndexedAccess.errors.txt @@ -37,8 +37,8 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccess.ts(318,5): error TS232 type K02 = keyof number; // "toString" | "toFixed" | "toExponential" | ... type K03 = keyof boolean; // "valueOf" type K04 = keyof void; // never - type K05 = keyof undefined; // never - type K06 = keyof null; // never + type K05 = keyof undefined; // string | number | symbol + type K06 = keyof null; // string | number | symbol type K07 = keyof never; // string | number | symbol type K08 = keyof unknown; // never diff --git a/tests/baselines/reference/keyofAndIndexedAccess.js b/tests/baselines/reference/keyofAndIndexedAccess.js index f7c34bdb3329f..091223233e0c4 100644 --- a/tests/baselines/reference/keyofAndIndexedAccess.js +++ b/tests/baselines/reference/keyofAndIndexedAccess.js @@ -29,8 +29,8 @@ type K01 = keyof string; // "toString" | "charAt" | ... type K02 = keyof number; // "toString" | "toFixed" | "toExponential" | ... type K03 = keyof boolean; // "valueOf" type K04 = keyof void; // never -type K05 = keyof undefined; // never -type K06 = keyof null; // never +type K05 = keyof undefined; // string | number | symbol +type K06 = keyof null; // string | number | symbol type K07 = keyof never; // string | number | symbol type K08 = keyof unknown; // never diff --git a/tests/baselines/reference/keyofAndIndexedAccess.symbols b/tests/baselines/reference/keyofAndIndexedAccess.symbols index 277a012517b16..dcb801eac03e9 100644 --- a/tests/baselines/reference/keyofAndIndexedAccess.symbols +++ b/tests/baselines/reference/keyofAndIndexedAccess.symbols @@ -73,10 +73,10 @@ type K03 = keyof boolean; // "valueOf" type K04 = keyof void; // never >K04 : Symbol(K04, Decl(keyofAndIndexedAccess.ts, 28, 25)) -type K05 = keyof undefined; // never +type K05 = keyof undefined; // string | number | symbol >K05 : Symbol(K05, Decl(keyofAndIndexedAccess.ts, 29, 22)) -type K06 = keyof null; // never +type K06 = keyof null; // string | number | symbol >K06 : Symbol(K06, Decl(keyofAndIndexedAccess.ts, 30, 27)) type K07 = keyof never; // string | number | symbol diff --git a/tests/baselines/reference/keyofAndIndexedAccess.types b/tests/baselines/reference/keyofAndIndexedAccess.types index fc4603b997ff7..e65564387f5a3 100644 --- a/tests/baselines/reference/keyofAndIndexedAccess.types +++ b/tests/baselines/reference/keyofAndIndexedAccess.types @@ -69,11 +69,11 @@ type K03 = keyof boolean; // "valueOf" type K04 = keyof void; // never >K04 : never -type K05 = keyof undefined; // never ->K05 : never +type K05 = keyof undefined; // string | number | symbol +>K05 : string | number | symbol -type K06 = keyof null; // never ->K06 : never +type K06 = keyof null; // string | number | symbol +>K06 : string | number | symbol >null : null type K07 = keyof never; // string | number | symbol diff --git a/tests/cases/conformance/types/keyof/keyofAndIndexedAccess.ts b/tests/cases/conformance/types/keyof/keyofAndIndexedAccess.ts index 56ff157a67fd0..2d528f7abded1 100644 --- a/tests/cases/conformance/types/keyof/keyofAndIndexedAccess.ts +++ b/tests/cases/conformance/types/keyof/keyofAndIndexedAccess.ts @@ -31,8 +31,8 @@ type K01 = keyof string; // "toString" | "charAt" | ... type K02 = keyof number; // "toString" | "toFixed" | "toExponential" | ... type K03 = keyof boolean; // "valueOf" type K04 = keyof void; // never -type K05 = keyof undefined; // never -type K06 = keyof null; // never +type K05 = keyof undefined; // string | number | symbol +type K06 = keyof null; // string | number | symbol type K07 = keyof never; // string | number | symbol type K08 = keyof unknown; // never From 313ae0d18a5432e5889d7b688c3525ad4b21df9e Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 28 Jun 2022 11:38:43 -1000 Subject: [PATCH 3/7] Defer types like `keyof (T & {})` --- src/compiler/checker.ts | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ea0988f84e804..d3995362a08a1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15238,24 +15238,28 @@ namespace ts { * type parameters in the union with a special never type that is treated as a literal in `getReducedType`, we can cause the `getReducedType` logic * to reduce the resulting type if possible (since only intersections with conflicting literal-typed properties are reducible). */ - function isPossiblyReducibleByInstantiation(type: UnionType): boolean { - return some(type.types, t => { - const uniqueFilled = getUniqueLiteralFilledInstantiation(t); - return getReducedType(uniqueFilled) !== uniqueFilled; - }); + function isPossiblyReducibleByInstantiation(type: Type): boolean { + const uniqueFilled = getUniqueLiteralFilledInstantiation(type); + return getReducedType(uniqueFilled) !== uniqueFilled; + } + + function shouldDeferIndexType(type: Type) { + return !!(type.flags & TypeFlags.InstantiableNonPrimitive || + isGenericTupleType(type) || + isGenericMappedType(type) && !hasDistributiveNameType(type) || + type.flags & TypeFlags.Union && some((type as UnionType).types, isPossiblyReducibleByInstantiation) || + type.flags & TypeFlags.Intersection && maybeTypeOfKind(type, TypeFlags.Instantiable) && some((type as IntersectionType).types, isEmptyAnonymousObjectType)); } function getIndexType(type: Type, stringsOnly = keyofStringsOnly, noIndexSignatures?: boolean): Type { type = getReducedType(type); - return type.flags & TypeFlags.Union ? isPossiblyReducibleByInstantiation(type as UnionType) - ? getIndexTypeForGenericType(type as InstantiableType | UnionOrIntersectionType, stringsOnly) - : getIntersectionType(map((type as UnionType).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) : + return shouldDeferIndexType(type) ? getIndexTypeForGenericType(type as InstantiableType | UnionOrIntersectionType, stringsOnly) : + type.flags & TypeFlags.Union ? getIntersectionType(map((type as UnionType).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) : type.flags & TypeFlags.Intersection ? getUnionType(map((type as IntersectionType).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) : - type.flags & TypeFlags.InstantiableNonPrimitive || isGenericTupleType(type) || isGenericMappedType(type) && !hasDistributiveNameType(type) ? getIndexTypeForGenericType(type as InstantiableType | UnionOrIntersectionType, stringsOnly) : getObjectFlags(type) & ObjectFlags.Mapped ? getIndexTypeForMappedType(type as MappedType, stringsOnly, noIndexSignatures) : type === wildcardType ? wildcardType : type.flags & TypeFlags.Unknown ? neverType : - type.flags & (TypeFlags.Any | TypeFlags.Nullable | TypeFlags.Never) ? keyofConstraintType : + type.flags & (TypeFlags.Any | TypeFlags.Never) ? keyofConstraintType : getLiteralTypeFromProperties(type, (noIndexSignatures ? TypeFlags.StringLiteral : TypeFlags.StringLike) | (stringsOnly ? 0 : TypeFlags.NumberLike | TypeFlags.ESSymbolLike), stringsOnly === keyofStringsOnly && !noIndexSignatures); } From f51abbff9fc3809e2eff903b4be0aec0d9c1132d Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 28 Jun 2022 11:38:58 -1000 Subject: [PATCH 4/7] Restore test --- tests/cases/conformance/types/keyof/keyofAndIndexedAccess.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/cases/conformance/types/keyof/keyofAndIndexedAccess.ts b/tests/cases/conformance/types/keyof/keyofAndIndexedAccess.ts index 2d528f7abded1..56ff157a67fd0 100644 --- a/tests/cases/conformance/types/keyof/keyofAndIndexedAccess.ts +++ b/tests/cases/conformance/types/keyof/keyofAndIndexedAccess.ts @@ -31,8 +31,8 @@ type K01 = keyof string; // "toString" | "charAt" | ... type K02 = keyof number; // "toString" | "toFixed" | "toExponential" | ... type K03 = keyof boolean; // "valueOf" type K04 = keyof void; // never -type K05 = keyof undefined; // string | number | symbol -type K06 = keyof null; // string | number | symbol +type K05 = keyof undefined; // never +type K06 = keyof null; // never type K07 = keyof never; // string | number | symbol type K08 = keyof unknown; // never From 877cfb3351ed54e2354b7865f8e0554744cf3b03 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 28 Jun 2022 11:47:51 -1000 Subject: [PATCH 5/7] Update test --- tests/cases/conformance/types/mapped/mappedTypes4.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cases/conformance/types/mapped/mappedTypes4.ts b/tests/cases/conformance/types/mapped/mappedTypes4.ts index 4def192d602d8..a2ff2ad4348b8 100644 --- a/tests/cases/conformance/types/mapped/mappedTypes4.ts +++ b/tests/cases/conformance/types/mapped/mappedTypes4.ts @@ -65,7 +65,7 @@ var x1: DeepReadonlyFoo; type Z = { a: number }; type Clone = { - [P in keyof (T & {})]: T[P]; + [P in keyof (T & {})]: (T & {})[P]; }; type M = Clone; // M should be { a: number } From b457868938aa5dd90da98559c65adea23ad557d6 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 28 Jun 2022 11:53:40 -1000 Subject: [PATCH 6/7] Accept new baselines --- .../baselines/reference/keyofAndIndexedAccess.errors.txt | 4 ++-- tests/baselines/reference/keyofAndIndexedAccess.js | 4 ++-- tests/baselines/reference/keyofAndIndexedAccess.symbols | 4 ++-- tests/baselines/reference/keyofAndIndexedAccess.types | 8 ++++---- tests/baselines/reference/mappedTypes4.js | 4 ++-- tests/baselines/reference/mappedTypes4.symbols | 2 +- tests/baselines/reference/mappedTypes4.types | 4 ++-- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/tests/baselines/reference/keyofAndIndexedAccess.errors.txt b/tests/baselines/reference/keyofAndIndexedAccess.errors.txt index 40e14a8643937..8917c5e588822 100644 --- a/tests/baselines/reference/keyofAndIndexedAccess.errors.txt +++ b/tests/baselines/reference/keyofAndIndexedAccess.errors.txt @@ -37,8 +37,8 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccess.ts(318,5): error TS232 type K02 = keyof number; // "toString" | "toFixed" | "toExponential" | ... type K03 = keyof boolean; // "valueOf" type K04 = keyof void; // never - type K05 = keyof undefined; // string | number | symbol - type K06 = keyof null; // string | number | symbol + type K05 = keyof undefined; // never + type K06 = keyof null; // never type K07 = keyof never; // string | number | symbol type K08 = keyof unknown; // never diff --git a/tests/baselines/reference/keyofAndIndexedAccess.js b/tests/baselines/reference/keyofAndIndexedAccess.js index 091223233e0c4..f7c34bdb3329f 100644 --- a/tests/baselines/reference/keyofAndIndexedAccess.js +++ b/tests/baselines/reference/keyofAndIndexedAccess.js @@ -29,8 +29,8 @@ type K01 = keyof string; // "toString" | "charAt" | ... type K02 = keyof number; // "toString" | "toFixed" | "toExponential" | ... type K03 = keyof boolean; // "valueOf" type K04 = keyof void; // never -type K05 = keyof undefined; // string | number | symbol -type K06 = keyof null; // string | number | symbol +type K05 = keyof undefined; // never +type K06 = keyof null; // never type K07 = keyof never; // string | number | symbol type K08 = keyof unknown; // never diff --git a/tests/baselines/reference/keyofAndIndexedAccess.symbols b/tests/baselines/reference/keyofAndIndexedAccess.symbols index dcb801eac03e9..277a012517b16 100644 --- a/tests/baselines/reference/keyofAndIndexedAccess.symbols +++ b/tests/baselines/reference/keyofAndIndexedAccess.symbols @@ -73,10 +73,10 @@ type K03 = keyof boolean; // "valueOf" type K04 = keyof void; // never >K04 : Symbol(K04, Decl(keyofAndIndexedAccess.ts, 28, 25)) -type K05 = keyof undefined; // string | number | symbol +type K05 = keyof undefined; // never >K05 : Symbol(K05, Decl(keyofAndIndexedAccess.ts, 29, 22)) -type K06 = keyof null; // string | number | symbol +type K06 = keyof null; // never >K06 : Symbol(K06, Decl(keyofAndIndexedAccess.ts, 30, 27)) type K07 = keyof never; // string | number | symbol diff --git a/tests/baselines/reference/keyofAndIndexedAccess.types b/tests/baselines/reference/keyofAndIndexedAccess.types index e65564387f5a3..fc4603b997ff7 100644 --- a/tests/baselines/reference/keyofAndIndexedAccess.types +++ b/tests/baselines/reference/keyofAndIndexedAccess.types @@ -69,11 +69,11 @@ type K03 = keyof boolean; // "valueOf" type K04 = keyof void; // never >K04 : never -type K05 = keyof undefined; // string | number | symbol ->K05 : string | number | symbol +type K05 = keyof undefined; // never +>K05 : never -type K06 = keyof null; // string | number | symbol ->K06 : string | number | symbol +type K06 = keyof null; // never +>K06 : never >null : null type K07 = keyof never; // string | number | symbol diff --git a/tests/baselines/reference/mappedTypes4.js b/tests/baselines/reference/mappedTypes4.js index 6237476906a75..f856fbcdf153c 100644 --- a/tests/baselines/reference/mappedTypes4.js +++ b/tests/baselines/reference/mappedTypes4.js @@ -63,7 +63,7 @@ var x1: DeepReadonlyFoo; type Z = { a: number }; type Clone = { - [P in keyof (T & {})]: T[P]; + [P in keyof (T & {})]: (T & {})[P]; }; type M = Clone; // M should be { a: number } @@ -144,7 +144,7 @@ declare type Z = { a: number; }; declare type Clone = { - [P in keyof (T & {})]: T[P]; + [P in keyof (T & {})]: (T & {})[P]; }; declare type M = Clone; declare var z1: Z; diff --git a/tests/baselines/reference/mappedTypes4.symbols b/tests/baselines/reference/mappedTypes4.symbols index 487f91c214e7b..e4c6e6872b3c8 100644 --- a/tests/baselines/reference/mappedTypes4.symbols +++ b/tests/baselines/reference/mappedTypes4.symbols @@ -205,7 +205,7 @@ type Clone = { >Clone : Symbol(Clone, Decl(mappedTypes4.ts, 62, 23)) >T : Symbol(T, Decl(mappedTypes4.ts, 63, 11)) - [P in keyof (T & {})]: T[P]; + [P in keyof (T & {})]: (T & {})[P]; >P : Symbol(P, Decl(mappedTypes4.ts, 64, 3)) >T : Symbol(T, Decl(mappedTypes4.ts, 63, 11)) >T : Symbol(T, Decl(mappedTypes4.ts, 63, 11)) diff --git a/tests/baselines/reference/mappedTypes4.types b/tests/baselines/reference/mappedTypes4.types index b51aefa9cce98..81e5b163d4661 100644 --- a/tests/baselines/reference/mappedTypes4.types +++ b/tests/baselines/reference/mappedTypes4.types @@ -160,10 +160,10 @@ type Z = { a: number }; type Clone = { >Clone : Clone - [P in keyof (T & {})]: T[P]; + [P in keyof (T & {})]: (T & {})[P]; }; type M = Clone; // M should be { a: number } ->M : Clone +>M : { a: number; } var z1: Z; >z1 : Z From 4062ab94fd1bd289bcc0bde2a053bd4ed7216263 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 29 Jun 2022 08:00:11 -1000 Subject: [PATCH 7/7] Add tests --- .../reference/unknownControlFlow.errors.txt | 46 +++++++++- .../baselines/reference/unknownControlFlow.js | 60 +++++++++++++ .../reference/unknownControlFlow.symbols | 85 +++++++++++++++++++ .../reference/unknownControlFlow.types | 84 ++++++++++++++++++ .../types/unknown/unknownControlFlow.ts | 32 +++++++ 5 files changed, 306 insertions(+), 1 deletion(-) diff --git a/tests/baselines/reference/unknownControlFlow.errors.txt b/tests/baselines/reference/unknownControlFlow.errors.txt index 65108ba22011f..7c6db9cbd2cae 100644 --- a/tests/baselines/reference/unknownControlFlow.errors.txt +++ b/tests/baselines/reference/unknownControlFlow.errors.txt @@ -1,7 +1,11 @@ tests/cases/conformance/types/unknown/unknownControlFlow.ts(18,9): error TS2322: Type 'unknown' is not assignable to type '{}'. +tests/cases/conformance/types/unknown/unknownControlFlow.ts(283,5): error TS2536: Type 'keyof (T & {})' cannot be used to index type 'T'. +tests/cases/conformance/types/unknown/unknownControlFlow.ts(290,11): error TS2345: Argument of type 'string' is not assignable to parameter of type 'never'. +tests/cases/conformance/types/unknown/unknownControlFlow.ts(291,5): error TS2345: Argument of type 'null' is not assignable to parameter of type 'never'. +tests/cases/conformance/types/unknown/unknownControlFlow.ts(293,5): error TS2345: Argument of type 'null' is not assignable to parameter of type 'never'. -==== tests/cases/conformance/types/unknown/unknownControlFlow.ts (1 errors) ==== +==== tests/cases/conformance/types/unknown/unknownControlFlow.ts (5 errors) ==== type T01 = {} & string; // string type T02 = {} & 'a'; // 'a' type T03 = {} & object; // object @@ -272,4 +276,44 @@ tests/cases/conformance/types/unknown/unknownControlFlow.ts(18,9): error TS2322: y; } } + + // We allow an unconstrained object of a generic type `T` to be indexed by a key of type `keyof T` + // without a check that the object is non-undefined and non-null. This is safe because `keyof T` + // is `never` (meaning no possible keys) for any `T` that includes `undefined` or `null`. + + function ff1(t: T, k: keyof T) { + t[k]; + } + + function ff2(t: T & {}, k: keyof T) { + t[k]; + } + + function ff3(t: T, k: keyof (T & {})) { + t[k]; // Error + ~~~~ +!!! error TS2536: Type 'keyof (T & {})' cannot be used to index type 'T'. + } + + function ff4(t: T & {}, k: keyof (T & {})) { + t[k]; + } + + ff1(null, 'foo'); // Error + ~~~~~ +!!! error TS2345: Argument of type 'string' is not assignable to parameter of type 'never'. + ff2(null, 'foo'); // Error + ~~~~ +!!! error TS2345: Argument of type 'null' is not assignable to parameter of type 'never'. + ff3(null, 'foo'); + ff4(null, 'foo'); // Error + ~~~~ +!!! error TS2345: Argument of type 'null' is not assignable to parameter of type 'never'. + + // Repro from #49681 + + type Foo = { [key: string]: unknown }; + type NullableFoo = Foo | undefined; + + type Bar = NonNullable[string]; \ No newline at end of file diff --git a/tests/baselines/reference/unknownControlFlow.js b/tests/baselines/reference/unknownControlFlow.js index d665377767cca..b924cf83ffb83 100644 --- a/tests/baselines/reference/unknownControlFlow.js +++ b/tests/baselines/reference/unknownControlFlow.js @@ -267,6 +267,38 @@ function foo(x: T | null) { y; } } + +// We allow an unconstrained object of a generic type `T` to be indexed by a key of type `keyof T` +// without a check that the object is non-undefined and non-null. This is safe because `keyof T` +// is `never` (meaning no possible keys) for any `T` that includes `undefined` or `null`. + +function ff1(t: T, k: keyof T) { + t[k]; +} + +function ff2(t: T & {}, k: keyof T) { + t[k]; +} + +function ff3(t: T, k: keyof (T & {})) { + t[k]; // Error +} + +function ff4(t: T & {}, k: keyof (T & {})) { + t[k]; +} + +ff1(null, 'foo'); // Error +ff2(null, 'foo'); // Error +ff3(null, 'foo'); +ff4(null, 'foo'); // Error + +// Repro from #49681 + +type Foo = { [key: string]: unknown }; +type NullableFoo = Foo | undefined; + +type Bar = NonNullable[string]; //// [unknownControlFlow.js] @@ -501,6 +533,25 @@ function foo(x) { y; } } +// We allow an unconstrained object of a generic type `T` to be indexed by a key of type `keyof T` +// without a check that the object is non-undefined and non-null. This is safe because `keyof T` +// is `never` (meaning no possible keys) for any `T` that includes `undefined` or `null`. +function ff1(t, k) { + t[k]; +} +function ff2(t, k) { + t[k]; +} +function ff3(t, k) { + t[k]; // Error +} +function ff4(t, k) { + t[k]; +} +ff1(null, 'foo'); // Error +ff2(null, 'foo'); // Error +ff3(null, 'foo'); +ff4(null, 'foo'); // Error //// [unknownControlFlow.d.ts] @@ -541,3 +592,12 @@ declare type QQ = NonNullable>>; declare function f41(a: T): void; declare function deepEquals(a: T, b: T): boolean; declare function foo(x: T | null): void; +declare function ff1(t: T, k: keyof T): void; +declare function ff2(t: T & {}, k: keyof T): void; +declare function ff3(t: T, k: keyof (T & {})): void; +declare function ff4(t: T & {}, k: keyof (T & {})): void; +declare type Foo = { + [key: string]: unknown; +}; +declare type NullableFoo = Foo | undefined; +declare type Bar = NonNullable[string]; diff --git a/tests/baselines/reference/unknownControlFlow.symbols b/tests/baselines/reference/unknownControlFlow.symbols index 176df97b14518..47b6d4411a530 100644 --- a/tests/baselines/reference/unknownControlFlow.symbols +++ b/tests/baselines/reference/unknownControlFlow.symbols @@ -636,3 +636,88 @@ function foo(x: T | null) { } } +// We allow an unconstrained object of a generic type `T` to be indexed by a key of type `keyof T` +// without a check that the object is non-undefined and non-null. This is safe because `keyof T` +// is `never` (meaning no possible keys) for any `T` that includes `undefined` or `null`. + +function ff1(t: T, k: keyof T) { +>ff1 : Symbol(ff1, Decl(unknownControlFlow.ts, 267, 1)) +>T : Symbol(T, Decl(unknownControlFlow.ts, 273, 13)) +>t : Symbol(t, Decl(unknownControlFlow.ts, 273, 16)) +>T : Symbol(T, Decl(unknownControlFlow.ts, 273, 13)) +>k : Symbol(k, Decl(unknownControlFlow.ts, 273, 21)) +>T : Symbol(T, Decl(unknownControlFlow.ts, 273, 13)) + + t[k]; +>t : Symbol(t, Decl(unknownControlFlow.ts, 273, 16)) +>k : Symbol(k, Decl(unknownControlFlow.ts, 273, 21)) +} + +function ff2(t: T & {}, k: keyof T) { +>ff2 : Symbol(ff2, Decl(unknownControlFlow.ts, 275, 1)) +>T : Symbol(T, Decl(unknownControlFlow.ts, 277, 13)) +>t : Symbol(t, Decl(unknownControlFlow.ts, 277, 16)) +>T : Symbol(T, Decl(unknownControlFlow.ts, 277, 13)) +>k : Symbol(k, Decl(unknownControlFlow.ts, 277, 26)) +>T : Symbol(T, Decl(unknownControlFlow.ts, 277, 13)) + + t[k]; +>t : Symbol(t, Decl(unknownControlFlow.ts, 277, 16)) +>k : Symbol(k, Decl(unknownControlFlow.ts, 277, 26)) +} + +function ff3(t: T, k: keyof (T & {})) { +>ff3 : Symbol(ff3, Decl(unknownControlFlow.ts, 279, 1)) +>T : Symbol(T, Decl(unknownControlFlow.ts, 281, 13)) +>t : Symbol(t, Decl(unknownControlFlow.ts, 281, 16)) +>T : Symbol(T, Decl(unknownControlFlow.ts, 281, 13)) +>k : Symbol(k, Decl(unknownControlFlow.ts, 281, 21)) +>T : Symbol(T, Decl(unknownControlFlow.ts, 281, 13)) + + t[k]; // Error +>t : Symbol(t, Decl(unknownControlFlow.ts, 281, 16)) +>k : Symbol(k, Decl(unknownControlFlow.ts, 281, 21)) +} + +function ff4(t: T & {}, k: keyof (T & {})) { +>ff4 : Symbol(ff4, Decl(unknownControlFlow.ts, 283, 1)) +>T : Symbol(T, Decl(unknownControlFlow.ts, 285, 13)) +>t : Symbol(t, Decl(unknownControlFlow.ts, 285, 16)) +>T : Symbol(T, Decl(unknownControlFlow.ts, 285, 13)) +>k : Symbol(k, Decl(unknownControlFlow.ts, 285, 26)) +>T : Symbol(T, Decl(unknownControlFlow.ts, 285, 13)) + + t[k]; +>t : Symbol(t, Decl(unknownControlFlow.ts, 285, 16)) +>k : Symbol(k, Decl(unknownControlFlow.ts, 285, 26)) +} + +ff1(null, 'foo'); // Error +>ff1 : Symbol(ff1, Decl(unknownControlFlow.ts, 267, 1)) + +ff2(null, 'foo'); // Error +>ff2 : Symbol(ff2, Decl(unknownControlFlow.ts, 275, 1)) + +ff3(null, 'foo'); +>ff3 : Symbol(ff3, Decl(unknownControlFlow.ts, 279, 1)) + +ff4(null, 'foo'); // Error +>ff4 : Symbol(ff4, Decl(unknownControlFlow.ts, 283, 1)) + +// Repro from #49681 + +type Foo = { [key: string]: unknown }; +>Foo : Symbol(Foo, Decl(unknownControlFlow.ts, 292, 17)) +>key : Symbol(key, Decl(unknownControlFlow.ts, 296, 14)) + +type NullableFoo = Foo | undefined; +>NullableFoo : Symbol(NullableFoo, Decl(unknownControlFlow.ts, 296, 38)) +>Foo : Symbol(Foo, Decl(unknownControlFlow.ts, 292, 17)) + +type Bar = NonNullable[string]; +>Bar : Symbol(Bar, Decl(unknownControlFlow.ts, 297, 35)) +>T : Symbol(T, Decl(unknownControlFlow.ts, 299, 9)) +>NullableFoo : Symbol(NullableFoo, Decl(unknownControlFlow.ts, 296, 38)) +>NonNullable : Symbol(NonNullable, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(unknownControlFlow.ts, 299, 9)) + diff --git a/tests/baselines/reference/unknownControlFlow.types b/tests/baselines/reference/unknownControlFlow.types index cfd88dbffc810..bd1d851c5b3a9 100644 --- a/tests/baselines/reference/unknownControlFlow.types +++ b/tests/baselines/reference/unknownControlFlow.types @@ -718,3 +718,87 @@ function foo(x: T | null) { } } +// We allow an unconstrained object of a generic type `T` to be indexed by a key of type `keyof T` +// without a check that the object is non-undefined and non-null. This is safe because `keyof T` +// is `never` (meaning no possible keys) for any `T` that includes `undefined` or `null`. + +function ff1(t: T, k: keyof T) { +>ff1 : (t: T, k: keyof T) => void +>t : T +>k : keyof T + + t[k]; +>t[k] : T[keyof T] +>t : T +>k : keyof T +} + +function ff2(t: T & {}, k: keyof T) { +>ff2 : (t: T & {}, k: keyof T) => void +>t : T & {} +>k : keyof T + + t[k]; +>t[k] : (T & {})[keyof T] +>t : T & {} +>k : keyof T +} + +function ff3(t: T, k: keyof (T & {})) { +>ff3 : (t: T, k: keyof (T & {})) => void +>t : T +>k : keyof (T & {}) + + t[k]; // Error +>t[k] : any +>t : T +>k : keyof (T & {}) +} + +function ff4(t: T & {}, k: keyof (T & {})) { +>ff4 : (t: T & {}, k: keyof (T & {})) => void +>t : T & {} +>k : keyof (T & {}) + + t[k]; +>t[k] : (T & {})[keyof (T & {})] +>t : T & {} +>k : keyof (T & {}) +} + +ff1(null, 'foo'); // Error +>ff1(null, 'foo') : void +>ff1 : (t: T, k: keyof T) => void +>null : null +>'foo' : "foo" + +ff2(null, 'foo'); // Error +>ff2(null, 'foo') : void +>ff2 : (t: T & {}, k: keyof T) => void +>null : null +>'foo' : "foo" + +ff3(null, 'foo'); +>ff3(null, 'foo') : void +>ff3 : (t: T, k: keyof (T & {})) => void +>null : null +>'foo' : "foo" + +ff4(null, 'foo'); // Error +>ff4(null, 'foo') : void +>ff4 : (t: T & {}, k: keyof (T & {})) => void +>null : null +>'foo' : "foo" + +// Repro from #49681 + +type Foo = { [key: string]: unknown }; +>Foo : { [key: string]: unknown; } +>key : string + +type NullableFoo = Foo | undefined; +>NullableFoo : Foo | undefined + +type Bar = NonNullable[string]; +>Bar : Bar + diff --git a/tests/cases/conformance/types/unknown/unknownControlFlow.ts b/tests/cases/conformance/types/unknown/unknownControlFlow.ts index 5bf8f0f2946da..55072dfddc470 100644 --- a/tests/cases/conformance/types/unknown/unknownControlFlow.ts +++ b/tests/cases/conformance/types/unknown/unknownControlFlow.ts @@ -269,3 +269,35 @@ function foo(x: T | null) { y; } } + +// We allow an unconstrained object of a generic type `T` to be indexed by a key of type `keyof T` +// without a check that the object is non-undefined and non-null. This is safe because `keyof T` +// is `never` (meaning no possible keys) for any `T` that includes `undefined` or `null`. + +function ff1(t: T, k: keyof T) { + t[k]; +} + +function ff2(t: T & {}, k: keyof T) { + t[k]; +} + +function ff3(t: T, k: keyof (T & {})) { + t[k]; // Error +} + +function ff4(t: T & {}, k: keyof (T & {})) { + t[k]; +} + +ff1(null, 'foo'); // Error +ff2(null, 'foo'); // Error +ff3(null, 'foo'); +ff4(null, 'foo'); // Error + +// Repro from #49681 + +type Foo = { [key: string]: unknown }; +type NullableFoo = Foo | undefined; + +type Bar = NonNullable[string];