From 7f0590d6a17687b1ba5465c1e8a7426a2b1f0cd0 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 26 Mar 2024 11:18:21 -0700 Subject: [PATCH 1/7] Examine resolved members to detect optionality --- src/compiler/checker.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ad13bd597db39..0b84d81fceb84 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -18635,11 +18635,26 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return !!(getUnionType([intersectTypes(type1, type2), neverType]).flags & TypeFlags.Never); } + // Given an indexed access on a mapped type of the form { [P in K]: E }[X], return an instantiation of E where P is + // replaced with X. Since this simplification doesn't account for mapped type modifiers, add 'undefined' to the + // resulting type if the mapped type includes a '?' modifier or if the modifiers type indicates that some properties + // are optional. If the modifiers type is generic, conservatively estimate optionality by recursively looking for + // mapped types that include '?' modifiers. function substituteIndexedMappedType(objectType: MappedType, index: Type) { const mapper = createTypeMapper([getTypeParameterFromMappedType(objectType)], [index]); const templateMapper = combineTypeMappers(objectType.mapper, mapper); const instantiatedTemplateType = instantiateType(getTemplateTypeFromMappedType(objectType.target as MappedType || objectType), templateMapper); - return addOptionality(instantiatedTemplateType, /*isProperty*/ true, getCombinedMappedTypeOptionality(objectType) > 0); + const isOptional = getMappedTypeOptionality(objectType) > 0 || (isGenericType(objectType) ? + getModifiersTypeOptionality(getModifiersTypeFromMappedType(objectType)) > 0 : + couldAccessOptionalProperty(objectType, index)); + return addOptionality(instantiatedTemplateType, /*isProperty*/ true, isOptional); + } + + // Return true if an indexed access with the given object and index types could access an optional property. + function couldAccessOptionalProperty(objectType: Type, indexType: Type) { + const indexConstraint = getBaseConstraintOfType(indexType); + return !!indexConstraint && some(getPropertiesOfType(objectType), p => !!(p.flags & SymbolFlags.Optional) && + isTypeAssignableTo(getLiteralTypeFromProperty(p, TypeFlags.StringOrNumberLiteralOrUnique), indexConstraint)); } function getIndexedAccessType(objectType: Type, indexType: Type, accessFlags = AccessFlags.None, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { From 2ce512147682b6d4e2d171668d5e4cb4adcc4b5f Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 26 Mar 2024 11:18:33 -0700 Subject: [PATCH 2/7] Add regression test --- ...ppedTypeIndexedAccessConstraint.errors.txt | 22 +++++++ .../mappedTypeIndexedAccessConstraint.symbols | 64 +++++++++++++++++++ .../mappedTypeIndexedAccessConstraint.types | 58 +++++++++++++++++ .../mappedTypeIndexedAccessConstraint.ts | 22 +++++++ 4 files changed, 166 insertions(+) diff --git a/tests/baselines/reference/mappedTypeIndexedAccessConstraint.errors.txt b/tests/baselines/reference/mappedTypeIndexedAccessConstraint.errors.txt index a04e49d09b272..c4e39fc2214ff 100644 --- a/tests/baselines/reference/mappedTypeIndexedAccessConstraint.errors.txt +++ b/tests/baselines/reference/mappedTypeIndexedAccessConstraint.errors.txt @@ -72,4 +72,26 @@ mappedTypeIndexedAccessConstraint.ts(53,34): error TS2722: Cannot invoke an obje const resolveMapper2 = ( key: K, o: MapperArgs) => mapper[key]?.(o) + + // Repro from #57860 + + type Obj1 = { + a: string; + b: number; + }; + + type Obj2 = { + b: number; + c: boolean; + }; + + declare const mapIntersection: { + [K in keyof (Partial & Required)]: number; + }; + + const accessMapped = (key: K) => mapIntersection[key].toString(); + + declare const resolved: { a?: number | undefined; b: number; c: number }; + + const accessResolved = (key: K) => resolved[key].toString(); \ No newline at end of file diff --git a/tests/baselines/reference/mappedTypeIndexedAccessConstraint.symbols b/tests/baselines/reference/mappedTypeIndexedAccessConstraint.symbols index 96d7dd12eb001..2ce48ebb59182 100644 --- a/tests/baselines/reference/mappedTypeIndexedAccessConstraint.symbols +++ b/tests/baselines/reference/mappedTypeIndexedAccessConstraint.symbols @@ -225,3 +225,67 @@ const resolveMapper2 = ( >key : Symbol(key, Decl(mappedTypeIndexedAccessConstraint.ts, 54, 55)) >o : Symbol(o, Decl(mappedTypeIndexedAccessConstraint.ts, 55, 11)) +// Repro from #57860 + +type Obj1 = { +>Obj1 : Symbol(Obj1, Decl(mappedTypeIndexedAccessConstraint.ts, 55, 49)) + + a: string; +>a : Symbol(a, Decl(mappedTypeIndexedAccessConstraint.ts, 59, 13)) + + b: number; +>b : Symbol(b, Decl(mappedTypeIndexedAccessConstraint.ts, 60, 14)) + +}; + +type Obj2 = { +>Obj2 : Symbol(Obj2, Decl(mappedTypeIndexedAccessConstraint.ts, 62, 2)) + + b: number; +>b : Symbol(b, Decl(mappedTypeIndexedAccessConstraint.ts, 64, 13)) + + c: boolean; +>c : Symbol(c, Decl(mappedTypeIndexedAccessConstraint.ts, 65, 14)) + +}; + +declare const mapIntersection: { +>mapIntersection : Symbol(mapIntersection, Decl(mappedTypeIndexedAccessConstraint.ts, 69, 13)) + + [K in keyof (Partial & Required)]: number; +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 70, 5)) +>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --)) +>Obj1 : Symbol(Obj1, Decl(mappedTypeIndexedAccessConstraint.ts, 55, 49)) +>Required : Symbol(Required, Decl(lib.es5.d.ts, --, --)) +>Obj2 : Symbol(Obj2, Decl(mappedTypeIndexedAccessConstraint.ts, 62, 2)) + +}; + +const accessMapped = (key: K) => mapIntersection[key].toString(); +>accessMapped : Symbol(accessMapped, Decl(mappedTypeIndexedAccessConstraint.ts, 73, 5)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 73, 22)) +>Obj2 : Symbol(Obj2, Decl(mappedTypeIndexedAccessConstraint.ts, 62, 2)) +>key : Symbol(key, Decl(mappedTypeIndexedAccessConstraint.ts, 73, 44)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 73, 22)) +>mapIntersection[key].toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) +>mapIntersection : Symbol(mapIntersection, Decl(mappedTypeIndexedAccessConstraint.ts, 69, 13)) +>key : Symbol(key, Decl(mappedTypeIndexedAccessConstraint.ts, 73, 44)) +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + +declare const resolved: { a?: number | undefined; b: number; c: number }; +>resolved : Symbol(resolved, Decl(mappedTypeIndexedAccessConstraint.ts, 75, 13)) +>a : Symbol(a, Decl(mappedTypeIndexedAccessConstraint.ts, 75, 25)) +>b : Symbol(b, Decl(mappedTypeIndexedAccessConstraint.ts, 75, 49)) +>c : Symbol(c, Decl(mappedTypeIndexedAccessConstraint.ts, 75, 60)) + +const accessResolved = (key: K) => resolved[key].toString(); +>accessResolved : Symbol(accessResolved, Decl(mappedTypeIndexedAccessConstraint.ts, 77, 5)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 77, 24)) +>Obj2 : Symbol(Obj2, Decl(mappedTypeIndexedAccessConstraint.ts, 62, 2)) +>key : Symbol(key, Decl(mappedTypeIndexedAccessConstraint.ts, 77, 46)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 77, 24)) +>resolved[key].toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) +>resolved : Symbol(resolved, Decl(mappedTypeIndexedAccessConstraint.ts, 75, 13)) +>key : Symbol(key, Decl(mappedTypeIndexedAccessConstraint.ts, 77, 46)) +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + diff --git a/tests/baselines/reference/mappedTypeIndexedAccessConstraint.types b/tests/baselines/reference/mappedTypeIndexedAccessConstraint.types index f833835d46a95..e4d227d9d43e3 100644 --- a/tests/baselines/reference/mappedTypeIndexedAccessConstraint.types +++ b/tests/baselines/reference/mappedTypeIndexedAccessConstraint.types @@ -191,3 +191,61 @@ const resolveMapper2 = ( >key : K >o : MapperArgs +// Repro from #57860 + +type Obj1 = { +>Obj1 : { a: string; b: number; } + + a: string; +>a : string + + b: number; +>b : number + +}; + +type Obj2 = { +>Obj2 : { b: number; c: boolean; } + + b: number; +>b : number + + c: boolean; +>c : boolean + +}; + +declare const mapIntersection: { +>mapIntersection : { a?: number | undefined; b: number; c: number; } + + [K in keyof (Partial & Required)]: number; +}; + +const accessMapped = (key: K) => mapIntersection[key].toString(); +>accessMapped : (key: K) => string +>(key: K) => mapIntersection[key].toString() : (key: K) => string +>key : K +>mapIntersection[key].toString() : string +>mapIntersection[key].toString : (radix?: number | undefined) => string +>mapIntersection[key] : { a?: number | undefined; b: number; c: number; }[K] +>mapIntersection : { a?: number | undefined; b: number; c: number; } +>key : K +>toString : (radix?: number | undefined) => string + +declare const resolved: { a?: number | undefined; b: number; c: number }; +>resolved : { a?: number | undefined; b: number; c: number; } +>a : number | undefined +>b : number +>c : number + +const accessResolved = (key: K) => resolved[key].toString(); +>accessResolved : (key: K) => string +>(key: K) => resolved[key].toString() : (key: K) => string +>key : K +>resolved[key].toString() : string +>resolved[key].toString : (radix?: number | undefined) => string +>resolved[key] : { a?: number | undefined; b: number; c: number; }[K] +>resolved : { a?: number | undefined; b: number; c: number; } +>key : K +>toString : (radix?: number | undefined) => string + diff --git a/tests/cases/compiler/mappedTypeIndexedAccessConstraint.ts b/tests/cases/compiler/mappedTypeIndexedAccessConstraint.ts index bf03035870f0b..6ae41d431d577 100644 --- a/tests/cases/compiler/mappedTypeIndexedAccessConstraint.ts +++ b/tests/cases/compiler/mappedTypeIndexedAccessConstraint.ts @@ -57,3 +57,25 @@ const resolveMapper1 = ( const resolveMapper2 = ( key: K, o: MapperArgs) => mapper[key]?.(o) + +// Repro from #57860 + +type Obj1 = { + a: string; + b: number; +}; + +type Obj2 = { + b: number; + c: boolean; +}; + +declare const mapIntersection: { + [K in keyof (Partial & Required)]: number; +}; + +const accessMapped = (key: K) => mapIntersection[key].toString(); + +declare const resolved: { a?: number | undefined; b: number; c: number }; + +const accessResolved = (key: K) => resolved[key].toString(); From 616770921f86c2d99659af9f6a40e080b39b98b2 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 26 Mar 2024 11:37:58 -0700 Subject: [PATCH 3/7] Less aggressive optionality analysis for intersections --- 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 0b84d81fceb84..6ee847f446923 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14115,7 +14115,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function getModifiersTypeOptionality(type: Type): number { - return type.flags & TypeFlags.Intersection ? Math.max(...map((type as IntersectionType).types, getModifiersTypeOptionality)) : + return type.flags & TypeFlags.Intersection ? Math.min(...map((type as IntersectionType).types, getModifiersTypeOptionality)) : getObjectFlags(type) & ObjectFlags.Mapped ? getCombinedMappedTypeOptionality(type as MappedType) : 0; } From d6a8a257b72a0bf2b81c2b03976537ac50182208 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 26 Mar 2024 11:38:13 -0700 Subject: [PATCH 4/7] Additional test --- ...ppedTypeIndexedAccessConstraint.errors.txt | 10 +++++++ .../mappedTypeIndexedAccessConstraint.symbols | 30 +++++++++++++++++++ .../mappedTypeIndexedAccessConstraint.types | 22 ++++++++++++++ .../mappedTypeIndexedAccessConstraint.ts | 10 +++++++ 4 files changed, 72 insertions(+) diff --git a/tests/baselines/reference/mappedTypeIndexedAccessConstraint.errors.txt b/tests/baselines/reference/mappedTypeIndexedAccessConstraint.errors.txt index c4e39fc2214ff..39b15dcee0e0e 100644 --- a/tests/baselines/reference/mappedTypeIndexedAccessConstraint.errors.txt +++ b/tests/baselines/reference/mappedTypeIndexedAccessConstraint.errors.txt @@ -94,4 +94,14 @@ mappedTypeIndexedAccessConstraint.ts(53,34): error TS2722: Cannot invoke an obje declare const resolved: { a?: number | undefined; b: number; c: number }; const accessResolved = (key: K) => resolved[key].toString(); + + // Additional repro from #57860 + + type Foo = { + prop: string; + } + + function test(obj: Pick & Partial, K>, key: K) { + obj[key].length; + } \ No newline at end of file diff --git a/tests/baselines/reference/mappedTypeIndexedAccessConstraint.symbols b/tests/baselines/reference/mappedTypeIndexedAccessConstraint.symbols index 2ce48ebb59182..a8ba03f424eca 100644 --- a/tests/baselines/reference/mappedTypeIndexedAccessConstraint.symbols +++ b/tests/baselines/reference/mappedTypeIndexedAccessConstraint.symbols @@ -289,3 +289,33 @@ const accessResolved = (key: K) => resolved[key].toString( >key : Symbol(key, Decl(mappedTypeIndexedAccessConstraint.ts, 77, 46)) >toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) +// Additional repro from #57860 + +type Foo = { +>Foo : Symbol(Foo, Decl(mappedTypeIndexedAccessConstraint.ts, 77, 82)) + + prop: string; +>prop : Symbol(prop, Decl(mappedTypeIndexedAccessConstraint.ts, 81, 12)) +} + +function test(obj: Pick & Partial, K>, key: K) { +>test : Symbol(test, Decl(mappedTypeIndexedAccessConstraint.ts, 83, 1)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 85, 14)) +>Foo : Symbol(Foo, Decl(mappedTypeIndexedAccessConstraint.ts, 77, 82)) +>obj : Symbol(obj, Decl(mappedTypeIndexedAccessConstraint.ts, 85, 35)) +>Pick : Symbol(Pick, Decl(lib.es5.d.ts, --, --)) +>Required : Symbol(Required, Decl(lib.es5.d.ts, --, --)) +>Foo : Symbol(Foo, Decl(mappedTypeIndexedAccessConstraint.ts, 77, 82)) +>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --)) +>Foo : Symbol(Foo, Decl(mappedTypeIndexedAccessConstraint.ts, 77, 82)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 85, 14)) +>key : Symbol(key, Decl(mappedTypeIndexedAccessConstraint.ts, 85, 78)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 85, 14)) + + obj[key].length; +>obj[key].length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) +>obj : Symbol(obj, Decl(mappedTypeIndexedAccessConstraint.ts, 85, 35)) +>key : Symbol(key, Decl(mappedTypeIndexedAccessConstraint.ts, 85, 78)) +>length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) +} + diff --git a/tests/baselines/reference/mappedTypeIndexedAccessConstraint.types b/tests/baselines/reference/mappedTypeIndexedAccessConstraint.types index e4d227d9d43e3..9a26de77d99b7 100644 --- a/tests/baselines/reference/mappedTypeIndexedAccessConstraint.types +++ b/tests/baselines/reference/mappedTypeIndexedAccessConstraint.types @@ -249,3 +249,25 @@ const accessResolved = (key: K) => resolved[key].toString( >key : K >toString : (radix?: number | undefined) => string +// Additional repro from #57860 + +type Foo = { +>Foo : { prop: string; } + + prop: string; +>prop : string +} + +function test(obj: Pick & Partial, K>, key: K) { +>test : (obj: Pick & Partial, K>, key: K) => void +>obj : Pick & Partial, K> +>key : K + + obj[key].length; +>obj[key].length : number +>obj[key] : Pick & Partial, K>[K] +>obj : Pick & Partial, K> +>key : K +>length : number +} + diff --git a/tests/cases/compiler/mappedTypeIndexedAccessConstraint.ts b/tests/cases/compiler/mappedTypeIndexedAccessConstraint.ts index 6ae41d431d577..893334f51dce3 100644 --- a/tests/cases/compiler/mappedTypeIndexedAccessConstraint.ts +++ b/tests/cases/compiler/mappedTypeIndexedAccessConstraint.ts @@ -79,3 +79,13 @@ const accessMapped = (key: K) => mapIntersection[key].toSt declare const resolved: { a?: number | undefined; b: number; c: number }; const accessResolved = (key: K) => resolved[key].toString(); + +// Additional repro from #57860 + +type Foo = { + prop: string; +} + +function test(obj: Pick & Partial, K>, key: K) { + obj[key].length; +} From 609d3f7c60ae1ec56d3eceaf3f33be57e7495cb6 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 26 Mar 2024 11:53:13 -0700 Subject: [PATCH 5/7] Fix formatting --- src/compiler/checker.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6ee847f446923..6fc11efc7d02c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -18653,7 +18653,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // Return true if an indexed access with the given object and index types could access an optional property. function couldAccessOptionalProperty(objectType: Type, indexType: Type) { const indexConstraint = getBaseConstraintOfType(indexType); - return !!indexConstraint && some(getPropertiesOfType(objectType), p => !!(p.flags & SymbolFlags.Optional) && + return !!indexConstraint && some(getPropertiesOfType(objectType), p => + !!(p.flags & SymbolFlags.Optional) && isTypeAssignableTo(getLiteralTypeFromProperty(p, TypeFlags.StringOrNumberLiteralOrUnique), indexConstraint)); } From a2790b5ea815abbcbbc5fa65d34edb1f534e393a Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 27 Mar 2024 07:59:14 -0700 Subject: [PATCH 6/7] Require uniformity in intersection modifiers --- src/compiler/checker.ts | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c4d651a23d0b6..84d7af07df671 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14120,16 +14120,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return modifiers & MappedTypeModifiers.ExcludeOptional ? -1 : modifiers & MappedTypeModifiers.IncludeOptional ? 1 : 0; } - function getModifiersTypeOptionality(type: Type): number { - return type.flags & TypeFlags.Intersection ? Math.min(...map((type as IntersectionType).types, getModifiersTypeOptionality)) : - getObjectFlags(type) & ObjectFlags.Mapped ? getCombinedMappedTypeOptionality(type as MappedType) : - 0; - } - // Return -1, 0, or 1, for stripped, unchanged, or added optionality respectively. When a homomorphic mapped type doesn't // modify optionality, recursively consult the optionality of the type being mapped over to see if it strips or adds optionality. - function getCombinedMappedTypeOptionality(type: MappedType): number { - return getMappedTypeOptionality(type) || getModifiersTypeOptionality(getModifiersTypeFromMappedType(type)); + // For intersections, return -1 or 1 when all constituents strip or add optionality, otherwise return 0. + function getCombinedMappedTypeOptionality(type: Type): number { + if (getObjectFlags(type) & ObjectFlags.Mapped) { + return getMappedTypeOptionality(type as MappedType) || getCombinedMappedTypeOptionality(getModifiersTypeFromMappedType(type as MappedType)); + } + if (type.flags & TypeFlags.Intersection) { + const optionality = getCombinedMappedTypeOptionality((type as IntersectionType).types[0]); + return every((type as IntersectionType).types, (t, i) => i === 0 || getCombinedMappedTypeOptionality(t) === optionality) ? optionality : 0; + } + return 0; } function isPartialMappedType(type: Type) { @@ -18651,7 +18653,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const templateMapper = combineTypeMappers(objectType.mapper, mapper); const instantiatedTemplateType = instantiateType(getTemplateTypeFromMappedType(objectType.target as MappedType || objectType), templateMapper); const isOptional = getMappedTypeOptionality(objectType) > 0 || (isGenericType(objectType) ? - getModifiersTypeOptionality(getModifiersTypeFromMappedType(objectType)) > 0 : + getCombinedMappedTypeOptionality(getModifiersTypeFromMappedType(objectType)) > 0 : couldAccessOptionalProperty(objectType, index)); return addOptionality(instantiatedTemplateType, /*isProperty*/ true, isOptional); } From 1b8bacb50f69ad93d4845c54c8a2c506beca0c40 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 27 Mar 2024 14:41:23 -0700 Subject: [PATCH 7/7] Accept new baselines --- .../mappedTypeIndexedAccessConstraint.types | 45 +++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/tests/baselines/reference/mappedTypeIndexedAccessConstraint.types b/tests/baselines/reference/mappedTypeIndexedAccessConstraint.types index f46bb2110311e..36ab99da95ca3 100644 --- a/tests/baselines/reference/mappedTypeIndexedAccessConstraint.types +++ b/tests/baselines/reference/mappedTypeIndexedAccessConstraint.types @@ -310,80 +310,119 @@ const resolveMapper2 = ( // Repro from #57860 type Obj1 = { ->Obj1 : { a: string; b: number; } +>Obj1 : Obj1 +> : ^^^^ a: string; >a : string +> : ^^^^^^ b: number; >b : number +> : ^^^^^^ }; type Obj2 = { ->Obj2 : { b: number; c: boolean; } +>Obj2 : Obj2 +> : ^^^^ b: number; >b : number +> : ^^^^^^ c: boolean; >c : boolean +> : ^^^^^^^ }; declare const mapIntersection: { >mapIntersection : { a?: number | undefined; b: number; c: number; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [K in keyof (Partial & Required)]: number; }; const accessMapped = (key: K) => mapIntersection[key].toString(); >accessMapped : (key: K) => string +> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^ >(key: K) => mapIntersection[key].toString() : (key: K) => string +> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^ >key : K +> : ^ >mapIntersection[key].toString() : string +> : ^^^^^^ >mapIntersection[key].toString : (radix?: number | undefined) => string +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >mapIntersection[key] : { a?: number | undefined; b: number; c: number; }[K] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >mapIntersection : { a?: number | undefined; b: number; c: number; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >key : K +> : ^ >toString : (radix?: number | undefined) => string +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ declare const resolved: { a?: number | undefined; b: number; c: number }; >resolved : { a?: number | undefined; b: number; c: number; } +> : ^^^^^^ ^^^^^ ^^^^^ ^^^ >a : number | undefined +> : ^^^^^^^^^^^^^^^^^^ >b : number +> : ^^^^^^ >c : number +> : ^^^^^^ const accessResolved = (key: K) => resolved[key].toString(); >accessResolved : (key: K) => string +> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^ >(key: K) => resolved[key].toString() : (key: K) => string +> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^ >key : K +> : ^ >resolved[key].toString() : string +> : ^^^^^^ >resolved[key].toString : (radix?: number | undefined) => string +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >resolved[key] : { a?: number | undefined; b: number; c: number; }[K] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >resolved : { a?: number | undefined; b: number; c: number; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >key : K +> : ^ >toString : (radix?: number | undefined) => string +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // Additional repro from #57860 type Foo = { ->Foo : { prop: string; } +>Foo : Foo +> : ^^^ prop: string; >prop : string +> : ^^^^^^ } function test(obj: Pick & Partial, K>, key: K) { >test : (obj: Pick & Partial, K>, key: K) => void +> : ^ ^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^ ^^^^^^^^^ >obj : Pick & Partial, K> +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >key : K +> : ^ obj[key].length; >obj[key].length : number +> : ^^^^^^ >obj[key] : Pick & Partial, K>[K] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >obj : Pick & Partial, K> +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >key : K +> : ^ >length : number +> : ^^^^^^ }