diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f9ab74b053256..0d12ca303980b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14035,15 +14035,23 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { (declaration.questionToken ? declaration.questionToken.kind === SyntaxKind.MinusToken ? MappedTypeModifiers.ExcludeOptional : MappedTypeModifiers.IncludeOptional : 0); } + // Return -1, 0, or 1, where -1 means optionality is stripped (i.e. -?), 0 means optionality is unchanged, and 1 means + // optionality is added (i.e. +?). function getMappedTypeOptionality(type: MappedType): number { const modifiers = getMappedTypeModifiers(type); return modifiers & MappedTypeModifiers.ExcludeOptional ? -1 : modifiers & MappedTypeModifiers.IncludeOptional ? 1 : 0; } + function getModifiersTypeOptionality(type: Type): number { + return type.flags & TypeFlags.Intersection ? Math.max(...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 { - const optionality = getMappedTypeOptionality(type); - const modifiersType = getModifiersTypeFromMappedType(type); - return optionality || (isGenericMappedType(modifiersType) ? getMappedTypeOptionality(modifiersType) : 0); + return getMappedTypeOptionality(type) || getModifiersTypeOptionality(getModifiersTypeFromMappedType(type)); } function isPartialMappedType(type: Type) { @@ -18517,7 +18525,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function substituteIndexedMappedType(objectType: MappedType, index: Type) { const mapper = createTypeMapper([getTypeParameterFromMappedType(objectType)], [index]); const templateMapper = combineTypeMappers(objectType.mapper, mapper); - return instantiateType(getTemplateTypeFromMappedType(objectType.target as MappedType || objectType), templateMapper); + const instantiatedTemplateType = instantiateType(getTemplateTypeFromMappedType(objectType.target as MappedType || objectType), templateMapper); + return addOptionality(instantiatedTemplateType, /*isProperty*/ true, getCombinedMappedTypeOptionality(objectType) > 0); } function getIndexedAccessType(objectType: Type, indexType: Type, accessFlags = AccessFlags.None, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { diff --git a/tests/baselines/reference/mappedTypeIndexedAccessConstraint.errors.txt b/tests/baselines/reference/mappedTypeIndexedAccessConstraint.errors.txt new file mode 100644 index 0000000000000..a04e49d09b272 --- /dev/null +++ b/tests/baselines/reference/mappedTypeIndexedAccessConstraint.errors.txt @@ -0,0 +1,75 @@ +mappedTypeIndexedAccessConstraint.ts(12,5): error TS18048: 'm1' is possibly 'undefined'. +mappedTypeIndexedAccessConstraint.ts(14,5): error TS18048: 'm2' is possibly 'undefined'. +mappedTypeIndexedAccessConstraint.ts(16,5): error TS18048: 'm3' is possibly 'undefined'. +mappedTypeIndexedAccessConstraint.ts(29,66): error TS2532: Object is possibly 'undefined'. +mappedTypeIndexedAccessConstraint.ts(53,34): error TS2722: Cannot invoke an object which is possibly 'undefined'. + + +==== mappedTypeIndexedAccessConstraint.ts (5 errors) ==== + type Identity = { [K in keyof T]: T[K] }; + + type M0 = { a: 1, b: 2 }; + + type M1 = { [K in keyof Partial]: M0[K] }; + + type M2 = { [K in keyof Required]: M1[K] }; + + type M3 = { [K in keyof Identity>]: M0[K] }; + + function foo(m1: M1[K], m2: M2[K], m3: M3[K]) { + m1.toString(); // Error + ~~ +!!! error TS18048: 'm1' is possibly 'undefined'. + m1?.toString(); + m2.toString(); // Error + ~~ +!!! error TS18048: 'm2' is possibly 'undefined'. + m2?.toString(); + m3.toString(); // Error + ~~ +!!! error TS18048: 'm3' is possibly 'undefined'. + m3?.toString(); + } + + // Repro from #57487 + + type Obj = { + a: 1, + b: 2 + }; + + const mapped: { [K in keyof Partial]: Obj[K] } = {}; + + const resolveMapped = (key: K) => mapped[key].toString(); // Error + ~~~~~~~~~~~ +!!! error TS2532: Object is possibly 'undefined'. + + // Additional repro from #57487 + + const arr = ["foo", "12", 42] as const; + + type Mappings = { foo: boolean, "12": number, 42: string }; + + type MapperArgs = { + v: K, + i: number + }; + + type SetOptional = Omit & Partial>; + + type PartMappings = SetOptional; + + const mapper: { [K in keyof PartMappings]: (o: MapperArgs) => PartMappings[K] } = { + foo: ({ v, i }) => v.length + i > 4, + "12": ({ v, i }) => Number(v) + i, + 42: ({ v, i }) => `${v}${i}`, + } + + const resolveMapper1 = ( + key: K, o: MapperArgs) => mapper[key](o); // Error + ~~~~~~~~~~~ +!!! error TS2722: Cannot invoke an object which is possibly 'undefined'. + + const resolveMapper2 = ( + key: K, o: MapperArgs) => mapper[key]?.(o) + \ No newline at end of file diff --git a/tests/baselines/reference/mappedTypeIndexedAccessConstraint.symbols b/tests/baselines/reference/mappedTypeIndexedAccessConstraint.symbols new file mode 100644 index 0000000000000..96d7dd12eb001 --- /dev/null +++ b/tests/baselines/reference/mappedTypeIndexedAccessConstraint.symbols @@ -0,0 +1,227 @@ +//// [tests/cases/compiler/mappedTypeIndexedAccessConstraint.ts] //// + +=== mappedTypeIndexedAccessConstraint.ts === +type Identity = { [K in keyof T]: T[K] }; +>Identity : Symbol(Identity, Decl(mappedTypeIndexedAccessConstraint.ts, 0, 0)) +>T : Symbol(T, Decl(mappedTypeIndexedAccessConstraint.ts, 0, 14)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 0, 22)) +>T : Symbol(T, Decl(mappedTypeIndexedAccessConstraint.ts, 0, 14)) +>T : Symbol(T, Decl(mappedTypeIndexedAccessConstraint.ts, 0, 14)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 0, 22)) + +type M0 = { a: 1, b: 2 }; +>M0 : Symbol(M0, Decl(mappedTypeIndexedAccessConstraint.ts, 0, 44)) +>a : Symbol(a, Decl(mappedTypeIndexedAccessConstraint.ts, 2, 11)) +>b : Symbol(b, Decl(mappedTypeIndexedAccessConstraint.ts, 2, 17)) + +type M1 = { [K in keyof Partial]: M0[K] }; +>M1 : Symbol(M1, Decl(mappedTypeIndexedAccessConstraint.ts, 2, 25)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 4, 13)) +>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --)) +>M0 : Symbol(M0, Decl(mappedTypeIndexedAccessConstraint.ts, 0, 44)) +>M0 : Symbol(M0, Decl(mappedTypeIndexedAccessConstraint.ts, 0, 44)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 4, 13)) + +type M2 = { [K in keyof Required]: M1[K] }; +>M2 : Symbol(M2, Decl(mappedTypeIndexedAccessConstraint.ts, 4, 46)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 6, 13)) +>Required : Symbol(Required, Decl(lib.es5.d.ts, --, --)) +>M1 : Symbol(M1, Decl(mappedTypeIndexedAccessConstraint.ts, 2, 25)) +>M1 : Symbol(M1, Decl(mappedTypeIndexedAccessConstraint.ts, 2, 25)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 6, 13)) + +type M3 = { [K in keyof Identity>]: M0[K] }; +>M3 : Symbol(M3, Decl(mappedTypeIndexedAccessConstraint.ts, 6, 47)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 8, 13)) +>Identity : Symbol(Identity, Decl(mappedTypeIndexedAccessConstraint.ts, 0, 0)) +>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --)) +>M0 : Symbol(M0, Decl(mappedTypeIndexedAccessConstraint.ts, 0, 44)) +>M0 : Symbol(M0, Decl(mappedTypeIndexedAccessConstraint.ts, 0, 44)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 8, 13)) + +function foo(m1: M1[K], m2: M2[K], m3: M3[K]) { +>foo : Symbol(foo, Decl(mappedTypeIndexedAccessConstraint.ts, 8, 56)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 10, 13)) +>M0 : Symbol(M0, Decl(mappedTypeIndexedAccessConstraint.ts, 0, 44)) +>m1 : Symbol(m1, Decl(mappedTypeIndexedAccessConstraint.ts, 10, 33)) +>M1 : Symbol(M1, Decl(mappedTypeIndexedAccessConstraint.ts, 2, 25)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 10, 13)) +>m2 : Symbol(m2, Decl(mappedTypeIndexedAccessConstraint.ts, 10, 43)) +>M2 : Symbol(M2, Decl(mappedTypeIndexedAccessConstraint.ts, 4, 46)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 10, 13)) +>m3 : Symbol(m3, Decl(mappedTypeIndexedAccessConstraint.ts, 10, 54)) +>M3 : Symbol(M3, Decl(mappedTypeIndexedAccessConstraint.ts, 6, 47)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 10, 13)) + + m1.toString(); // Error +>m1.toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) +>m1 : Symbol(m1, Decl(mappedTypeIndexedAccessConstraint.ts, 10, 33)) +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + + m1?.toString(); +>m1?.toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) +>m1 : Symbol(m1, Decl(mappedTypeIndexedAccessConstraint.ts, 10, 33)) +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + + m2.toString(); // Error +>m2.toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) +>m2 : Symbol(m2, Decl(mappedTypeIndexedAccessConstraint.ts, 10, 43)) +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + + m2?.toString(); +>m2?.toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) +>m2 : Symbol(m2, Decl(mappedTypeIndexedAccessConstraint.ts, 10, 43)) +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + + m3.toString(); // Error +>m3.toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) +>m3 : Symbol(m3, Decl(mappedTypeIndexedAccessConstraint.ts, 10, 54)) +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + + m3?.toString(); +>m3?.toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) +>m3 : Symbol(m3, Decl(mappedTypeIndexedAccessConstraint.ts, 10, 54)) +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) +} + +// Repro from #57487 + +type Obj = { +>Obj : Symbol(Obj, Decl(mappedTypeIndexedAccessConstraint.ts, 17, 1)) + + a: 1, +>a : Symbol(a, Decl(mappedTypeIndexedAccessConstraint.ts, 21, 12)) + + b: 2 +>b : Symbol(b, Decl(mappedTypeIndexedAccessConstraint.ts, 22, 9)) + +}; + +const mapped: { [K in keyof Partial]: Obj[K] } = {}; +>mapped : Symbol(mapped, Decl(mappedTypeIndexedAccessConstraint.ts, 26, 5)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 26, 17)) +>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --)) +>Obj : Symbol(Obj, Decl(mappedTypeIndexedAccessConstraint.ts, 17, 1)) +>Obj : Symbol(Obj, Decl(mappedTypeIndexedAccessConstraint.ts, 17, 1)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 26, 17)) + +const resolveMapped = (key: K) => mapped[key].toString(); // Error +>resolveMapped : Symbol(resolveMapped, Decl(mappedTypeIndexedAccessConstraint.ts, 28, 5)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 28, 23)) +>mapped : Symbol(mapped, Decl(mappedTypeIndexedAccessConstraint.ts, 26, 5)) +>key : Symbol(key, Decl(mappedTypeIndexedAccessConstraint.ts, 28, 54)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 28, 23)) +>mapped[key].toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) +>mapped : Symbol(mapped, Decl(mappedTypeIndexedAccessConstraint.ts, 26, 5)) +>key : Symbol(key, Decl(mappedTypeIndexedAccessConstraint.ts, 28, 54)) +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + +// Additional repro from #57487 + +const arr = ["foo", "12", 42] as const; +>arr : Symbol(arr, Decl(mappedTypeIndexedAccessConstraint.ts, 32, 5)) +>const : Symbol(const) + +type Mappings = { foo: boolean, "12": number, 42: string }; +>Mappings : Symbol(Mappings, Decl(mappedTypeIndexedAccessConstraint.ts, 32, 39)) +>foo : Symbol(foo, Decl(mappedTypeIndexedAccessConstraint.ts, 34, 17)) +>"12" : Symbol("12", Decl(mappedTypeIndexedAccessConstraint.ts, 34, 31)) +>42 : Symbol(42, Decl(mappedTypeIndexedAccessConstraint.ts, 34, 45)) + +type MapperArgs = { +>MapperArgs : Symbol(MapperArgs, Decl(mappedTypeIndexedAccessConstraint.ts, 34, 59)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 36, 16)) +>arr : Symbol(arr, Decl(mappedTypeIndexedAccessConstraint.ts, 32, 5)) + + v: K, +>v : Symbol(v, Decl(mappedTypeIndexedAccessConstraint.ts, 36, 51)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 36, 16)) + + i: number +>i : Symbol(i, Decl(mappedTypeIndexedAccessConstraint.ts, 37, 9)) + +}; + +type SetOptional = Omit & Partial>; +>SetOptional : Symbol(SetOptional, Decl(mappedTypeIndexedAccessConstraint.ts, 39, 2)) +>T : Symbol(T, Decl(mappedTypeIndexedAccessConstraint.ts, 41, 17)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 41, 19)) +>T : Symbol(T, Decl(mappedTypeIndexedAccessConstraint.ts, 41, 17)) +>Omit : Symbol(Omit, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(mappedTypeIndexedAccessConstraint.ts, 41, 17)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 41, 19)) +>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --)) +>Pick : Symbol(Pick, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(mappedTypeIndexedAccessConstraint.ts, 41, 17)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 41, 19)) + +type PartMappings = SetOptional; +>PartMappings : Symbol(PartMappings, Decl(mappedTypeIndexedAccessConstraint.ts, 41, 74)) +>SetOptional : Symbol(SetOptional, Decl(mappedTypeIndexedAccessConstraint.ts, 39, 2)) +>Mappings : Symbol(Mappings, Decl(mappedTypeIndexedAccessConstraint.ts, 32, 39)) + +const mapper: { [K in keyof PartMappings]: (o: MapperArgs) => PartMappings[K] } = { +>mapper : Symbol(mapper, Decl(mappedTypeIndexedAccessConstraint.ts, 45, 5)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 45, 17)) +>PartMappings : Symbol(PartMappings, Decl(mappedTypeIndexedAccessConstraint.ts, 41, 74)) +>o : Symbol(o, Decl(mappedTypeIndexedAccessConstraint.ts, 45, 44)) +>MapperArgs : Symbol(MapperArgs, Decl(mappedTypeIndexedAccessConstraint.ts, 34, 59)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 45, 17)) +>PartMappings : Symbol(PartMappings, Decl(mappedTypeIndexedAccessConstraint.ts, 41, 74)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 45, 17)) + + foo: ({ v, i }) => v.length + i > 4, +>foo : Symbol(foo, Decl(mappedTypeIndexedAccessConstraint.ts, 45, 86)) +>v : Symbol(v, Decl(mappedTypeIndexedAccessConstraint.ts, 46, 11)) +>i : Symbol(i, Decl(mappedTypeIndexedAccessConstraint.ts, 46, 14)) +>v.length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) +>v : Symbol(v, Decl(mappedTypeIndexedAccessConstraint.ts, 46, 11)) +>length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) +>i : Symbol(i, Decl(mappedTypeIndexedAccessConstraint.ts, 46, 14)) + + "12": ({ v, i }) => Number(v) + i, +>"12" : Symbol("12", Decl(mappedTypeIndexedAccessConstraint.ts, 46, 40)) +>v : Symbol(v, Decl(mappedTypeIndexedAccessConstraint.ts, 47, 12)) +>i : Symbol(i, Decl(mappedTypeIndexedAccessConstraint.ts, 47, 15)) +>Number : Symbol(Number, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>v : Symbol(v, Decl(mappedTypeIndexedAccessConstraint.ts, 47, 12)) +>i : Symbol(i, Decl(mappedTypeIndexedAccessConstraint.ts, 47, 15)) + + 42: ({ v, i }) => `${v}${i}`, +>42 : Symbol(42, Decl(mappedTypeIndexedAccessConstraint.ts, 47, 38)) +>v : Symbol(v, Decl(mappedTypeIndexedAccessConstraint.ts, 48, 10)) +>i : Symbol(i, Decl(mappedTypeIndexedAccessConstraint.ts, 48, 13)) +>v : Symbol(v, Decl(mappedTypeIndexedAccessConstraint.ts, 48, 10)) +>i : Symbol(i, Decl(mappedTypeIndexedAccessConstraint.ts, 48, 13)) +} + +const resolveMapper1 = ( +>resolveMapper1 : Symbol(resolveMapper1, Decl(mappedTypeIndexedAccessConstraint.ts, 51, 5)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 51, 24)) +>mapper : Symbol(mapper, Decl(mappedTypeIndexedAccessConstraint.ts, 45, 5)) + + key: K, o: MapperArgs) => mapper[key](o); // Error +>key : Symbol(key, Decl(mappedTypeIndexedAccessConstraint.ts, 51, 55)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 51, 24)) +>o : Symbol(o, Decl(mappedTypeIndexedAccessConstraint.ts, 52, 11)) +>MapperArgs : Symbol(MapperArgs, Decl(mappedTypeIndexedAccessConstraint.ts, 34, 59)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 51, 24)) +>mapper : Symbol(mapper, Decl(mappedTypeIndexedAccessConstraint.ts, 45, 5)) +>key : Symbol(key, Decl(mappedTypeIndexedAccessConstraint.ts, 51, 55)) +>o : Symbol(o, Decl(mappedTypeIndexedAccessConstraint.ts, 52, 11)) + +const resolveMapper2 = ( +>resolveMapper2 : Symbol(resolveMapper2, Decl(mappedTypeIndexedAccessConstraint.ts, 54, 5)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 54, 24)) +>mapper : Symbol(mapper, Decl(mappedTypeIndexedAccessConstraint.ts, 45, 5)) + + key: K, o: MapperArgs) => mapper[key]?.(o) +>key : Symbol(key, Decl(mappedTypeIndexedAccessConstraint.ts, 54, 55)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 54, 24)) +>o : Symbol(o, Decl(mappedTypeIndexedAccessConstraint.ts, 55, 11)) +>MapperArgs : Symbol(MapperArgs, Decl(mappedTypeIndexedAccessConstraint.ts, 34, 59)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 54, 24)) +>mapper : Symbol(mapper, Decl(mappedTypeIndexedAccessConstraint.ts, 45, 5)) +>key : Symbol(key, Decl(mappedTypeIndexedAccessConstraint.ts, 54, 55)) +>o : Symbol(o, Decl(mappedTypeIndexedAccessConstraint.ts, 55, 11)) + diff --git a/tests/baselines/reference/mappedTypeIndexedAccessConstraint.types b/tests/baselines/reference/mappedTypeIndexedAccessConstraint.types new file mode 100644 index 0000000000000..f833835d46a95 --- /dev/null +++ b/tests/baselines/reference/mappedTypeIndexedAccessConstraint.types @@ -0,0 +1,193 @@ +//// [tests/cases/compiler/mappedTypeIndexedAccessConstraint.ts] //// + +=== mappedTypeIndexedAccessConstraint.ts === +type Identity = { [K in keyof T]: T[K] }; +>Identity : Identity + +type M0 = { a: 1, b: 2 }; +>M0 : { a: 1; b: 2; } +>a : 1 +>b : 2 + +type M1 = { [K in keyof Partial]: M0[K] }; +>M1 : { a?: 1 | undefined; b?: 2 | undefined; } + +type M2 = { [K in keyof Required]: M1[K] }; +>M2 : { a: 1 | undefined; b: 2 | undefined; } + +type M3 = { [K in keyof Identity>]: M0[K] }; +>M3 : { a?: 1 | undefined; b?: 2 | undefined; } + +function foo(m1: M1[K], m2: M2[K], m3: M3[K]) { +>foo : (m1: M1[K], m2: M2[K], m3: M3[K]) => void +>m1 : M1[K] +>m2 : M2[K] +>m3 : M3[K] + + m1.toString(); // Error +>m1.toString() : string +>m1.toString : (radix?: number | undefined) => string +>m1 : 1 | 2 | undefined +>toString : (radix?: number | undefined) => string + + m1?.toString(); +>m1?.toString() : string | undefined +>m1?.toString : ((radix?: number | undefined) => string) | undefined +>m1 : 1 | 2 | undefined +>toString : ((radix?: number | undefined) => string) | undefined + + m2.toString(); // Error +>m2.toString() : string +>m2.toString : (radix?: number | undefined) => string +>m2 : 1 | 2 | undefined +>toString : (radix?: number | undefined) => string + + m2?.toString(); +>m2?.toString() : string | undefined +>m2?.toString : ((radix?: number | undefined) => string) | undefined +>m2 : 1 | 2 | undefined +>toString : ((radix?: number | undefined) => string) | undefined + + m3.toString(); // Error +>m3.toString() : string +>m3.toString : (radix?: number | undefined) => string +>m3 : 1 | 2 | undefined +>toString : (radix?: number | undefined) => string + + m3?.toString(); +>m3?.toString() : string | undefined +>m3?.toString : ((radix?: number | undefined) => string) | undefined +>m3 : 1 | 2 | undefined +>toString : ((radix?: number | undefined) => string) | undefined +} + +// Repro from #57487 + +type Obj = { +>Obj : { a: 1; b: 2; } + + a: 1, +>a : 1 + + b: 2 +>b : 2 + +}; + +const mapped: { [K in keyof Partial]: Obj[K] } = {}; +>mapped : { a?: 1 | undefined; b?: 2 | undefined; } +>{} : {} + +const resolveMapped = (key: K) => mapped[key].toString(); // Error +>resolveMapped : (key: K) => string +>(key: K) => mapped[key].toString() : (key: K) => string +>mapped : { a?: 1 | undefined; b?: 2 | undefined; } +>key : K +>mapped[key].toString() : string +>mapped[key].toString : (radix?: number | undefined) => string +>mapped[key] : 1 | 2 | undefined +>mapped : { a?: 1 | undefined; b?: 2 | undefined; } +>key : K +>toString : (radix?: number | undefined) => string + +// Additional repro from #57487 + +const arr = ["foo", "12", 42] as const; +>arr : readonly ["foo", "12", 42] +>["foo", "12", 42] as const : readonly ["foo", "12", 42] +>["foo", "12", 42] : readonly ["foo", "12", 42] +>"foo" : "foo" +>"12" : "12" +>42 : 42 + +type Mappings = { foo: boolean, "12": number, 42: string }; +>Mappings : { foo: boolean; "12": number; 42: string; } +>foo : boolean +>"12" : number +>42 : string + +type MapperArgs = { +>MapperArgs : MapperArgs +>arr : readonly ["foo", "12", 42] + + v: K, +>v : K + + i: number +>i : number + +}; + +type SetOptional = Omit & Partial>; +>SetOptional : SetOptional + +type PartMappings = SetOptional; +>PartMappings : Omit & Partial> + +const mapper: { [K in keyof PartMappings]: (o: MapperArgs) => PartMappings[K] } = { +>mapper : { "12": (o: MapperArgs<"12">) => number; 42: (o: MapperArgs<42>) => string; foo?: ((o: MapperArgs<"foo">) => boolean | undefined) | undefined; } +>o : MapperArgs +>{ foo: ({ v, i }) => v.length + i > 4, "12": ({ v, i }) => Number(v) + i, 42: ({ v, i }) => `${v}${i}`,} : { foo: ({ v, i }: MapperArgs<"foo">) => boolean; "12": ({ v, i }: MapperArgs<"12">) => number; 42: ({ v, i }: MapperArgs<42>) => string; } + + foo: ({ v, i }) => v.length + i > 4, +>foo : ({ v, i }: MapperArgs<"foo">) => boolean +>({ v, i }) => v.length + i > 4 : ({ v, i }: MapperArgs<"foo">) => boolean +>v : "foo" +>i : number +>v.length + i > 4 : boolean +>v.length + i : number +>v.length : number +>v : "foo" +>length : number +>i : number +>4 : 4 + + "12": ({ v, i }) => Number(v) + i, +>"12" : ({ v, i }: MapperArgs<"12">) => number +>({ v, i }) => Number(v) + i : ({ v, i }: MapperArgs<"12">) => number +>v : "12" +>i : number +>Number(v) + i : number +>Number(v) : number +>Number : NumberConstructor +>v : "12" +>i : number + + 42: ({ v, i }) => `${v}${i}`, +>42 : ({ v, i }: MapperArgs<42>) => string +>({ v, i }) => `${v}${i}` : ({ v, i }: MapperArgs<42>) => string +>v : 42 +>i : number +>`${v}${i}` : string +>v : 42 +>i : number +} + +const resolveMapper1 = ( +>resolveMapper1 : (key: K, o: MapperArgs) => PartMappings[K] +>( key: K, o: MapperArgs) => mapper[key](o) : (key: K, o: MapperArgs) => PartMappings[K] +>mapper : { "12": (o: MapperArgs<"12">) => number; 42: (o: MapperArgs<42>) => string; foo?: ((o: MapperArgs<"foo">) => boolean | undefined) | undefined; } + + key: K, o: MapperArgs) => mapper[key](o); // Error +>key : K +>o : MapperArgs +>mapper[key](o) : PartMappings[K] +>mapper[key] : ((o: MapperArgs) => PartMappings[K]) | undefined +>mapper : { "12": (o: MapperArgs<"12">) => number; 42: (o: MapperArgs<42>) => string; foo?: ((o: MapperArgs<"foo">) => boolean | undefined) | undefined; } +>key : K +>o : MapperArgs + +const resolveMapper2 = ( +>resolveMapper2 : (key: K, o: MapperArgs) => PartMappings[K] | undefined +>( key: K, o: MapperArgs) => mapper[key]?.(o) : (key: K, o: MapperArgs) => PartMappings[K] | undefined +>mapper : { "12": (o: MapperArgs<"12">) => number; 42: (o: MapperArgs<42>) => string; foo?: ((o: MapperArgs<"foo">) => boolean | undefined) | undefined; } + + key: K, o: MapperArgs) => mapper[key]?.(o) +>key : K +>o : MapperArgs +>mapper[key]?.(o) : PartMappings[K] | undefined +>mapper[key] : ((o: MapperArgs) => PartMappings[K]) | undefined +>mapper : { "12": (o: MapperArgs<"12">) => number; 42: (o: MapperArgs<42>) => string; foo?: ((o: MapperArgs<"foo">) => boolean | undefined) | undefined; } +>key : K +>o : MapperArgs + diff --git a/tests/cases/compiler/mappedTypeIndexedAccessConstraint.ts b/tests/cases/compiler/mappedTypeIndexedAccessConstraint.ts new file mode 100644 index 0000000000000..bf03035870f0b --- /dev/null +++ b/tests/cases/compiler/mappedTypeIndexedAccessConstraint.ts @@ -0,0 +1,59 @@ +// @strict: true +// @noEmit: true + +type Identity = { [K in keyof T]: T[K] }; + +type M0 = { a: 1, b: 2 }; + +type M1 = { [K in keyof Partial]: M0[K] }; + +type M2 = { [K in keyof Required]: M1[K] }; + +type M3 = { [K in keyof Identity>]: M0[K] }; + +function foo(m1: M1[K], m2: M2[K], m3: M3[K]) { + m1.toString(); // Error + m1?.toString(); + m2.toString(); // Error + m2?.toString(); + m3.toString(); // Error + m3?.toString(); +} + +// Repro from #57487 + +type Obj = { + a: 1, + b: 2 +}; + +const mapped: { [K in keyof Partial]: Obj[K] } = {}; + +const resolveMapped = (key: K) => mapped[key].toString(); // Error + +// Additional repro from #57487 + +const arr = ["foo", "12", 42] as const; + +type Mappings = { foo: boolean, "12": number, 42: string }; + +type MapperArgs = { + v: K, + i: number +}; + +type SetOptional = Omit & Partial>; + +type PartMappings = SetOptional; + +const mapper: { [K in keyof PartMappings]: (o: MapperArgs) => PartMappings[K] } = { + foo: ({ v, i }) => v.length + i > 4, + "12": ({ v, i }) => Number(v) + i, + 42: ({ v, i }) => `${v}${i}`, +} + +const resolveMapper1 = ( + key: K, o: MapperArgs) => mapper[key](o); // Error + +const resolveMapper2 = ( + key: K, o: MapperArgs) => mapper[key]?.(o)