From c2399d48e02d363994fa7056e298a17727873998 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Fri, 14 Oct 2022 13:00:23 +0200 Subject: [PATCH] Allow indexed access with known keys on otherwise deferrable indexed access on generic object --- src/compiler/checker.ts | 15 ++++++++++--- ...exedAccessWithKnownKeyOnDeferrable.symbols | 22 +++++++++++++++++++ ...ndexedAccessWithKnownKeyOnDeferrable.types | 16 ++++++++++++++ .../indexedAccessWithKnownKeyOnDeferrable.ts | 9 ++++++++ 4 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 tests/baselines/reference/indexedAccessWithKnownKeyOnDeferrable.symbols create mode 100644 tests/baselines/reference/indexedAccessWithKnownKeyOnDeferrable.types create mode 100644 tests/cases/compiler/indexedAccessWithKnownKeyOnDeferrable.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 72a6934b0413f..0dd1323040e9d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15571,10 +15571,9 @@ namespace ts { type.flags & TypeFlags.Intersection && maybeTypeOfKind(type, TypeFlags.Instantiable) && some((type as IntersectionType).types, isEmptyAnonymousObjectType)); } - function getIndexType(type: Type, stringsOnly = keyofStringsOnly, noIndexSignatures?: boolean): Type { + function getImmediateIndexType(type: Type, stringsOnly = keyofStringsOnly, noIndexSignatures?: boolean): Type { type = getReducedType(type); - return shouldDeferIndexType(type) ? getIndexTypeForGenericType(type as InstantiableType | UnionOrIntersectionType, stringsOnly) : - type.flags & TypeFlags.Union ? getIntersectionType(map((type as UnionType).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) : + return 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))) : getObjectFlags(type) & ObjectFlags.Mapped ? getIndexTypeForMappedType(type as MappedType, stringsOnly, noIndexSignatures) : type === wildcardType ? wildcardType : @@ -15584,6 +15583,13 @@ namespace ts { stringsOnly === keyofStringsOnly && !noIndexSignatures); } + function getIndexType(type: Type, stringsOnly = keyofStringsOnly, noIndexSignatures?: boolean): Type { + type = getReducedType(type); + return shouldDeferIndexType(type) + ? getIndexTypeForGenericType(type as InstantiableType | UnionOrIntersectionType, stringsOnly) + : getImmediateIndexType(type, stringsOnly, noIndexSignatures); + } + function getExtractStringType(type: Type) { if (keyofStringsOnly) { return type; @@ -36437,6 +36443,9 @@ namespace ts { return type; } if (isGenericObjectType(objectType)) { + if (isTypeAssignableTo(indexType, getImmediateIndexType(apparentObjectType, /*stringsOnly*/ false))) { + return type; + } const propertyName = getPropertyNameFromIndex(indexType, accessNode); if (propertyName) { const propertySymbol = forEachType(apparentObjectType, t => getPropertyOfType(t, propertyName)); diff --git a/tests/baselines/reference/indexedAccessWithKnownKeyOnDeferrable.symbols b/tests/baselines/reference/indexedAccessWithKnownKeyOnDeferrable.symbols new file mode 100644 index 0000000000000..b1b30bb78d17b --- /dev/null +++ b/tests/baselines/reference/indexedAccessWithKnownKeyOnDeferrable.symbols @@ -0,0 +1,22 @@ +=== tests/cases/compiler/indexedAccessWithKnownKeyOnDeferrable.ts === +// repro #51161 + +export type AnyOneof = { oneofKind: string; [k: string]: unknown } | { oneofKind: undefined }; +>AnyOneof : Symbol(AnyOneof, Decl(indexedAccessWithKnownKeyOnDeferrable.ts, 0, 0)) +>oneofKind : Symbol(oneofKind, Decl(indexedAccessWithKnownKeyOnDeferrable.ts, 2, 24)) +>k : Symbol(k, Decl(indexedAccessWithKnownKeyOnDeferrable.ts, 2, 45)) +>oneofKind : Symbol(oneofKind, Decl(indexedAccessWithKnownKeyOnDeferrable.ts, 2, 70)) + +export type AnyOneofKind = T extends { oneofKind: keyof T } +>AnyOneofKind : Symbol(AnyOneofKind, Decl(indexedAccessWithKnownKeyOnDeferrable.ts, 2, 94)) +>T : Symbol(T, Decl(indexedAccessWithKnownKeyOnDeferrable.ts, 3, 25)) +>AnyOneof : Symbol(AnyOneof, Decl(indexedAccessWithKnownKeyOnDeferrable.ts, 0, 0)) +>T : Symbol(T, Decl(indexedAccessWithKnownKeyOnDeferrable.ts, 3, 25)) +>oneofKind : Symbol(oneofKind, Decl(indexedAccessWithKnownKeyOnDeferrable.ts, 3, 58)) +>T : Symbol(T, Decl(indexedAccessWithKnownKeyOnDeferrable.ts, 3, 25)) + + ? T['oneofKind'] +>T : Symbol(T, Decl(indexedAccessWithKnownKeyOnDeferrable.ts, 3, 25)) + + : never; + diff --git a/tests/baselines/reference/indexedAccessWithKnownKeyOnDeferrable.types b/tests/baselines/reference/indexedAccessWithKnownKeyOnDeferrable.types new file mode 100644 index 0000000000000..106b466953cdd --- /dev/null +++ b/tests/baselines/reference/indexedAccessWithKnownKeyOnDeferrable.types @@ -0,0 +1,16 @@ +=== tests/cases/compiler/indexedAccessWithKnownKeyOnDeferrable.ts === +// repro #51161 + +export type AnyOneof = { oneofKind: string; [k: string]: unknown } | { oneofKind: undefined }; +>AnyOneof : { [k: string]: unknown; oneofKind: string; } | { oneofKind: undefined; } +>oneofKind : string +>k : string +>oneofKind : undefined + +export type AnyOneofKind = T extends { oneofKind: keyof T } +>AnyOneofKind : AnyOneofKind +>oneofKind : keyof T + + ? T['oneofKind'] + : never; + diff --git a/tests/cases/compiler/indexedAccessWithKnownKeyOnDeferrable.ts b/tests/cases/compiler/indexedAccessWithKnownKeyOnDeferrable.ts new file mode 100644 index 0000000000000..a612bd568b314 --- /dev/null +++ b/tests/cases/compiler/indexedAccessWithKnownKeyOnDeferrable.ts @@ -0,0 +1,9 @@ +// @strict: true +// @noEmit: true + +// repro #51161 + +export type AnyOneof = { oneofKind: string; [k: string]: unknown } | { oneofKind: undefined }; +export type AnyOneofKind = T extends { oneofKind: keyof T } + ? T['oneofKind'] + : never;