From 97095c1959e443dbe92abfa43b9549adf6a380ce Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 26 Feb 2024 09:18:19 -0800 Subject: [PATCH 1/5] Add optionality to mapped type indexed access substitutions --- src/compiler/checker.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f9ab74b053256..f32cfae030840 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14040,10 +14040,14 @@ 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.max(...map((type as IntersectionType).types, getModifiersTypeOptionality)) : + getObjectFlags(type) & ObjectFlags.Mapped ? getMappedTypeOptionality(type as MappedType) : + 0; + } + 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 +18521,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 { From ab3184b81cf413730075fcb0ffbb6590b3ef5652 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 26 Feb 2024 09:18:27 -0800 Subject: [PATCH 2/5] Add tests --- ...ppedTypeIndexedAccessConstraint.errors.txt | 66 ++++++ .../mappedTypeIndexedAccessConstraint.symbols | 197 ++++++++++++++++++ .../mappedTypeIndexedAccessConstraint.types | 174 ++++++++++++++++ .../mappedTypeIndexedAccessConstraint.ts | 53 +++++ 4 files changed, 490 insertions(+) create mode 100644 tests/baselines/reference/mappedTypeIndexedAccessConstraint.errors.txt create mode 100644 tests/baselines/reference/mappedTypeIndexedAccessConstraint.symbols create mode 100644 tests/baselines/reference/mappedTypeIndexedAccessConstraint.types create mode 100644 tests/cases/compiler/mappedTypeIndexedAccessConstraint.ts diff --git a/tests/baselines/reference/mappedTypeIndexedAccessConstraint.errors.txt b/tests/baselines/reference/mappedTypeIndexedAccessConstraint.errors.txt new file mode 100644 index 0000000000000..f7ac38617b31d --- /dev/null +++ b/tests/baselines/reference/mappedTypeIndexedAccessConstraint.errors.txt @@ -0,0 +1,66 @@ +mappedTypeIndexedAccessConstraint.ts(8,5): error TS18048: 'm1' is possibly 'undefined'. +mappedTypeIndexedAccessConstraint.ts(10,5): error TS18048: 'm2' is possibly 'undefined'. +mappedTypeIndexedAccessConstraint.ts(23,66): error TS2532: Object is possibly 'undefined'. +mappedTypeIndexedAccessConstraint.ts(47,34): error TS2722: Cannot invoke an object which is possibly 'undefined'. + + +==== mappedTypeIndexedAccessConstraint.ts (4 errors) ==== + type M0 = { a: 1, b: 2 }; + + type M1 = { [K in keyof Partial]: M0[K] }; + + type M2 = { [K in keyof Required]: M1[K] }; + + function foo(m1: M1[K], m2: M2[K]) { + m1.toString(); // Error + ~~ +!!! error TS18048: 'm1' is possibly 'undefined'. + m1?.toString(); + m2.toString(); // Error + ~~ +!!! error TS18048: 'm2' is possibly 'undefined'. + m2?.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..6f98ee94a48dc --- /dev/null +++ b/tests/baselines/reference/mappedTypeIndexedAccessConstraint.symbols @@ -0,0 +1,197 @@ +//// [tests/cases/compiler/mappedTypeIndexedAccessConstraint.ts] //// + +=== mappedTypeIndexedAccessConstraint.ts === +type M0 = { a: 1, b: 2 }; +>M0 : Symbol(M0, Decl(mappedTypeIndexedAccessConstraint.ts, 0, 0)) +>a : Symbol(a, Decl(mappedTypeIndexedAccessConstraint.ts, 0, 11)) +>b : Symbol(b, Decl(mappedTypeIndexedAccessConstraint.ts, 0, 17)) + +type M1 = { [K in keyof Partial]: M0[K] }; +>M1 : Symbol(M1, Decl(mappedTypeIndexedAccessConstraint.ts, 0, 25)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 2, 13)) +>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --)) +>M0 : Symbol(M0, Decl(mappedTypeIndexedAccessConstraint.ts, 0, 0)) +>M0 : Symbol(M0, Decl(mappedTypeIndexedAccessConstraint.ts, 0, 0)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 2, 13)) + +type M2 = { [K in keyof Required]: M1[K] }; +>M2 : Symbol(M2, Decl(mappedTypeIndexedAccessConstraint.ts, 2, 46)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 4, 13)) +>Required : Symbol(Required, Decl(lib.es5.d.ts, --, --)) +>M1 : Symbol(M1, Decl(mappedTypeIndexedAccessConstraint.ts, 0, 25)) +>M1 : Symbol(M1, Decl(mappedTypeIndexedAccessConstraint.ts, 0, 25)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 4, 13)) + +function foo(m1: M1[K], m2: M2[K]) { +>foo : Symbol(foo, Decl(mappedTypeIndexedAccessConstraint.ts, 4, 47)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 6, 13)) +>M0 : Symbol(M0, Decl(mappedTypeIndexedAccessConstraint.ts, 0, 0)) +>m1 : Symbol(m1, Decl(mappedTypeIndexedAccessConstraint.ts, 6, 33)) +>M1 : Symbol(M1, Decl(mappedTypeIndexedAccessConstraint.ts, 0, 25)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 6, 13)) +>m2 : Symbol(m2, Decl(mappedTypeIndexedAccessConstraint.ts, 6, 43)) +>M2 : Symbol(M2, Decl(mappedTypeIndexedAccessConstraint.ts, 2, 46)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 6, 13)) + + m1.toString(); // Error +>m1.toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) +>m1 : Symbol(m1, Decl(mappedTypeIndexedAccessConstraint.ts, 6, 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, 6, 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, 6, 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, 6, 43)) +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) +} + +// Repro from #57487 + +type Obj = { +>Obj : Symbol(Obj, Decl(mappedTypeIndexedAccessConstraint.ts, 11, 1)) + + a: 1, +>a : Symbol(a, Decl(mappedTypeIndexedAccessConstraint.ts, 15, 12)) + + b: 2 +>b : Symbol(b, Decl(mappedTypeIndexedAccessConstraint.ts, 16, 9)) + +}; + +const mapped: { [K in keyof Partial]: Obj[K] } = {}; +>mapped : Symbol(mapped, Decl(mappedTypeIndexedAccessConstraint.ts, 20, 5)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 20, 17)) +>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --)) +>Obj : Symbol(Obj, Decl(mappedTypeIndexedAccessConstraint.ts, 11, 1)) +>Obj : Symbol(Obj, Decl(mappedTypeIndexedAccessConstraint.ts, 11, 1)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 20, 17)) + +const resolveMapped = (key: K) => mapped[key].toString(); // Error +>resolveMapped : Symbol(resolveMapped, Decl(mappedTypeIndexedAccessConstraint.ts, 22, 5)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 22, 23)) +>mapped : Symbol(mapped, Decl(mappedTypeIndexedAccessConstraint.ts, 20, 5)) +>key : Symbol(key, Decl(mappedTypeIndexedAccessConstraint.ts, 22, 54)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 22, 23)) +>mapped[key].toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) +>mapped : Symbol(mapped, Decl(mappedTypeIndexedAccessConstraint.ts, 20, 5)) +>key : Symbol(key, Decl(mappedTypeIndexedAccessConstraint.ts, 22, 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, 26, 5)) +>const : Symbol(const) + +type Mappings = { foo: boolean, "12": number, 42: string }; +>Mappings : Symbol(Mappings, Decl(mappedTypeIndexedAccessConstraint.ts, 26, 39)) +>foo : Symbol(foo, Decl(mappedTypeIndexedAccessConstraint.ts, 28, 17)) +>"12" : Symbol("12", Decl(mappedTypeIndexedAccessConstraint.ts, 28, 31)) +>42 : Symbol(42, Decl(mappedTypeIndexedAccessConstraint.ts, 28, 45)) + +type MapperArgs = { +>MapperArgs : Symbol(MapperArgs, Decl(mappedTypeIndexedAccessConstraint.ts, 28, 59)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 30, 16)) +>arr : Symbol(arr, Decl(mappedTypeIndexedAccessConstraint.ts, 26, 5)) + + v: K, +>v : Symbol(v, Decl(mappedTypeIndexedAccessConstraint.ts, 30, 51)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 30, 16)) + + i: number +>i : Symbol(i, Decl(mappedTypeIndexedAccessConstraint.ts, 31, 9)) + +}; + +type SetOptional = Omit & Partial>; +>SetOptional : Symbol(SetOptional, Decl(mappedTypeIndexedAccessConstraint.ts, 33, 2)) +>T : Symbol(T, Decl(mappedTypeIndexedAccessConstraint.ts, 35, 17)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 35, 19)) +>T : Symbol(T, Decl(mappedTypeIndexedAccessConstraint.ts, 35, 17)) +>Omit : Symbol(Omit, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(mappedTypeIndexedAccessConstraint.ts, 35, 17)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 35, 19)) +>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --)) +>Pick : Symbol(Pick, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(mappedTypeIndexedAccessConstraint.ts, 35, 17)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 35, 19)) + +type PartMappings = SetOptional; +>PartMappings : Symbol(PartMappings, Decl(mappedTypeIndexedAccessConstraint.ts, 35, 74)) +>SetOptional : Symbol(SetOptional, Decl(mappedTypeIndexedAccessConstraint.ts, 33, 2)) +>Mappings : Symbol(Mappings, Decl(mappedTypeIndexedAccessConstraint.ts, 26, 39)) + +const mapper: { [K in keyof PartMappings]: (o: MapperArgs) => PartMappings[K] } = { +>mapper : Symbol(mapper, Decl(mappedTypeIndexedAccessConstraint.ts, 39, 5)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 39, 17)) +>PartMappings : Symbol(PartMappings, Decl(mappedTypeIndexedAccessConstraint.ts, 35, 74)) +>o : Symbol(o, Decl(mappedTypeIndexedAccessConstraint.ts, 39, 44)) +>MapperArgs : Symbol(MapperArgs, Decl(mappedTypeIndexedAccessConstraint.ts, 28, 59)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 39, 17)) +>PartMappings : Symbol(PartMappings, Decl(mappedTypeIndexedAccessConstraint.ts, 35, 74)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 39, 17)) + + foo: ({ v, i }) => v.length + i > 4, +>foo : Symbol(foo, Decl(mappedTypeIndexedAccessConstraint.ts, 39, 86)) +>v : Symbol(v, Decl(mappedTypeIndexedAccessConstraint.ts, 40, 11)) +>i : Symbol(i, Decl(mappedTypeIndexedAccessConstraint.ts, 40, 14)) +>v.length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) +>v : Symbol(v, Decl(mappedTypeIndexedAccessConstraint.ts, 40, 11)) +>length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) +>i : Symbol(i, Decl(mappedTypeIndexedAccessConstraint.ts, 40, 14)) + + "12": ({ v, i }) => Number(v) + i, +>"12" : Symbol("12", Decl(mappedTypeIndexedAccessConstraint.ts, 40, 40)) +>v : Symbol(v, Decl(mappedTypeIndexedAccessConstraint.ts, 41, 12)) +>i : Symbol(i, Decl(mappedTypeIndexedAccessConstraint.ts, 41, 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, 41, 12)) +>i : Symbol(i, Decl(mappedTypeIndexedAccessConstraint.ts, 41, 15)) + + 42: ({ v, i }) => `${v}${i}`, +>42 : Symbol(42, Decl(mappedTypeIndexedAccessConstraint.ts, 41, 38)) +>v : Symbol(v, Decl(mappedTypeIndexedAccessConstraint.ts, 42, 10)) +>i : Symbol(i, Decl(mappedTypeIndexedAccessConstraint.ts, 42, 13)) +>v : Symbol(v, Decl(mappedTypeIndexedAccessConstraint.ts, 42, 10)) +>i : Symbol(i, Decl(mappedTypeIndexedAccessConstraint.ts, 42, 13)) +} + +const resolveMapper1 = ( +>resolveMapper1 : Symbol(resolveMapper1, Decl(mappedTypeIndexedAccessConstraint.ts, 45, 5)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 45, 24)) +>mapper : Symbol(mapper, Decl(mappedTypeIndexedAccessConstraint.ts, 39, 5)) + + key: K, o: MapperArgs) => mapper[key](o); // Error +>key : Symbol(key, Decl(mappedTypeIndexedAccessConstraint.ts, 45, 55)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 45, 24)) +>o : Symbol(o, Decl(mappedTypeIndexedAccessConstraint.ts, 46, 11)) +>MapperArgs : Symbol(MapperArgs, Decl(mappedTypeIndexedAccessConstraint.ts, 28, 59)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 45, 24)) +>mapper : Symbol(mapper, Decl(mappedTypeIndexedAccessConstraint.ts, 39, 5)) +>key : Symbol(key, Decl(mappedTypeIndexedAccessConstraint.ts, 45, 55)) +>o : Symbol(o, Decl(mappedTypeIndexedAccessConstraint.ts, 46, 11)) + +const resolveMapper2 = ( +>resolveMapper2 : Symbol(resolveMapper2, Decl(mappedTypeIndexedAccessConstraint.ts, 48, 5)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 48, 24)) +>mapper : Symbol(mapper, Decl(mappedTypeIndexedAccessConstraint.ts, 39, 5)) + + key: K, o: MapperArgs) => mapper[key]?.(o) +>key : Symbol(key, Decl(mappedTypeIndexedAccessConstraint.ts, 48, 55)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 48, 24)) +>o : Symbol(o, Decl(mappedTypeIndexedAccessConstraint.ts, 49, 11)) +>MapperArgs : Symbol(MapperArgs, Decl(mappedTypeIndexedAccessConstraint.ts, 28, 59)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 48, 24)) +>mapper : Symbol(mapper, Decl(mappedTypeIndexedAccessConstraint.ts, 39, 5)) +>key : Symbol(key, Decl(mappedTypeIndexedAccessConstraint.ts, 48, 55)) +>o : Symbol(o, Decl(mappedTypeIndexedAccessConstraint.ts, 49, 11)) + diff --git a/tests/baselines/reference/mappedTypeIndexedAccessConstraint.types b/tests/baselines/reference/mappedTypeIndexedAccessConstraint.types new file mode 100644 index 0000000000000..2af4e80d4a4ab --- /dev/null +++ b/tests/baselines/reference/mappedTypeIndexedAccessConstraint.types @@ -0,0 +1,174 @@ +//// [tests/cases/compiler/mappedTypeIndexedAccessConstraint.ts] //// + +=== mappedTypeIndexedAccessConstraint.ts === +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; } + +function foo(m1: M1[K], m2: M2[K]) { +>foo : (m1: M1[K], m2: M2[K]) => void +>m1 : M1[K] +>m2 : M2[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 +} + +// 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..c26320e533eba --- /dev/null +++ b/tests/cases/compiler/mappedTypeIndexedAccessConstraint.ts @@ -0,0 +1,53 @@ +// @strict: true +// @noEmit: true + +type M0 = { a: 1, b: 2 }; + +type M1 = { [K in keyof Partial]: M0[K] }; + +type M2 = { [K in keyof Required]: M1[K] }; + +function foo(m1: M1[K], m2: M2[K]) { + m1.toString(); // Error + m1?.toString(); + m2.toString(); // Error + m2?.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) From 1de92c96550056d7e70eb655fc9a85382ede028f Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 27 Feb 2024 18:13:03 -0800 Subject: [PATCH 3/5] Examine modifiers types in depth --- 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 f32cfae030840..08537e69e5f77 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14042,7 +14042,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)) : - getObjectFlags(type) & ObjectFlags.Mapped ? getMappedTypeOptionality(type as MappedType) : + getObjectFlags(type) & ObjectFlags.Mapped ? getCombinedMappedTypeOptionality(type as MappedType) : 0; } From e83df762cb9fe8dd3da16c4b9378b336e72b936f Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 28 Feb 2024 07:40:41 -0800 Subject: [PATCH 4/5] Add test --- ...ppedTypeIndexedAccessConstraint.errors.txt | 21 +- .../mappedTypeIndexedAccessConstraint.symbols | 248 ++++++++++-------- .../mappedTypeIndexedAccessConstraint.types | 23 +- .../mappedTypeIndexedAccessConstraint.ts | 8 +- 4 files changed, 182 insertions(+), 118 deletions(-) diff --git a/tests/baselines/reference/mappedTypeIndexedAccessConstraint.errors.txt b/tests/baselines/reference/mappedTypeIndexedAccessConstraint.errors.txt index f7ac38617b31d..a04e49d09b272 100644 --- a/tests/baselines/reference/mappedTypeIndexedAccessConstraint.errors.txt +++ b/tests/baselines/reference/mappedTypeIndexedAccessConstraint.errors.txt @@ -1,17 +1,22 @@ -mappedTypeIndexedAccessConstraint.ts(8,5): error TS18048: 'm1' is possibly 'undefined'. -mappedTypeIndexedAccessConstraint.ts(10,5): error TS18048: 'm2' is possibly 'undefined'. -mappedTypeIndexedAccessConstraint.ts(23,66): error TS2532: Object is possibly 'undefined'. -mappedTypeIndexedAccessConstraint.ts(47,34): error TS2722: Cannot invoke an object which is possibly 'undefined'. +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 (4 errors) ==== +==== 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] }; - function foo(m1: M1[K], m2: M2[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'. @@ -20,6 +25,10 @@ mappedTypeIndexedAccessConstraint.ts(47,34): error TS2722: Cannot invoke an obje ~~ !!! error TS18048: 'm2' is possibly 'undefined'. m2?.toString(); + m3.toString(); // Error + ~~ +!!! error TS18048: 'm3' is possibly 'undefined'. + m3?.toString(); } // Repro from #57487 diff --git a/tests/baselines/reference/mappedTypeIndexedAccessConstraint.symbols b/tests/baselines/reference/mappedTypeIndexedAccessConstraint.symbols index 6f98ee94a48dc..96d7dd12eb001 100644 --- a/tests/baselines/reference/mappedTypeIndexedAccessConstraint.symbols +++ b/tests/baselines/reference/mappedTypeIndexedAccessConstraint.symbols @@ -1,197 +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, 0)) ->a : Symbol(a, Decl(mappedTypeIndexedAccessConstraint.ts, 0, 11)) ->b : Symbol(b, Decl(mappedTypeIndexedAccessConstraint.ts, 0, 17)) +>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, 0, 25)) ->K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 2, 13)) ->Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --)) ->M0 : Symbol(M0, Decl(mappedTypeIndexedAccessConstraint.ts, 0, 0)) ->M0 : Symbol(M0, Decl(mappedTypeIndexedAccessConstraint.ts, 0, 0)) ->K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 2, 13)) - -type M2 = { [K in keyof Required]: M1[K] }; ->M2 : Symbol(M2, Decl(mappedTypeIndexedAccessConstraint.ts, 2, 46)) +>M1 : Symbol(M1, Decl(mappedTypeIndexedAccessConstraint.ts, 2, 25)) >K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 4, 13)) ->Required : Symbol(Required, Decl(lib.es5.d.ts, --, --)) ->M1 : Symbol(M1, Decl(mappedTypeIndexedAccessConstraint.ts, 0, 25)) ->M1 : Symbol(M1, Decl(mappedTypeIndexedAccessConstraint.ts, 0, 25)) +>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)) -function foo(m1: M1[K], m2: M2[K]) { ->foo : Symbol(foo, Decl(mappedTypeIndexedAccessConstraint.ts, 4, 47)) ->K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 6, 13)) ->M0 : Symbol(M0, Decl(mappedTypeIndexedAccessConstraint.ts, 0, 0)) ->m1 : Symbol(m1, Decl(mappedTypeIndexedAccessConstraint.ts, 6, 33)) ->M1 : Symbol(M1, Decl(mappedTypeIndexedAccessConstraint.ts, 0, 25)) +type M2 = { [K in keyof Required]: M1[K] }; +>M2 : Symbol(M2, Decl(mappedTypeIndexedAccessConstraint.ts, 4, 46)) >K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 6, 13)) ->m2 : Symbol(m2, Decl(mappedTypeIndexedAccessConstraint.ts, 6, 43)) ->M2 : Symbol(M2, Decl(mappedTypeIndexedAccessConstraint.ts, 2, 46)) +>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, 6, 33)) +>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, 6, 33)) +>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, 6, 43)) +>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, 6, 43)) +>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, 11, 1)) +>Obj : Symbol(Obj, Decl(mappedTypeIndexedAccessConstraint.ts, 17, 1)) a: 1, ->a : Symbol(a, Decl(mappedTypeIndexedAccessConstraint.ts, 15, 12)) +>a : Symbol(a, Decl(mappedTypeIndexedAccessConstraint.ts, 21, 12)) b: 2 ->b : Symbol(b, Decl(mappedTypeIndexedAccessConstraint.ts, 16, 9)) +>b : Symbol(b, Decl(mappedTypeIndexedAccessConstraint.ts, 22, 9)) }; const mapped: { [K in keyof Partial]: Obj[K] } = {}; ->mapped : Symbol(mapped, Decl(mappedTypeIndexedAccessConstraint.ts, 20, 5)) ->K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 20, 17)) +>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, 11, 1)) ->Obj : Symbol(Obj, Decl(mappedTypeIndexedAccessConstraint.ts, 11, 1)) ->K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 20, 17)) +>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, 22, 5)) ->K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 22, 23)) ->mapped : Symbol(mapped, Decl(mappedTypeIndexedAccessConstraint.ts, 20, 5)) ->key : Symbol(key, Decl(mappedTypeIndexedAccessConstraint.ts, 22, 54)) ->K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 22, 23)) +>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, 20, 5)) ->key : Symbol(key, Decl(mappedTypeIndexedAccessConstraint.ts, 22, 54)) +>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, 26, 5)) +>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, 26, 39)) ->foo : Symbol(foo, Decl(mappedTypeIndexedAccessConstraint.ts, 28, 17)) ->"12" : Symbol("12", Decl(mappedTypeIndexedAccessConstraint.ts, 28, 31)) ->42 : Symbol(42, Decl(mappedTypeIndexedAccessConstraint.ts, 28, 45)) +>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, 28, 59)) ->K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 30, 16)) ->arr : Symbol(arr, Decl(mappedTypeIndexedAccessConstraint.ts, 26, 5)) +>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, 30, 51)) ->K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 30, 16)) +>v : Symbol(v, Decl(mappedTypeIndexedAccessConstraint.ts, 36, 51)) +>K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 36, 16)) i: number ->i : Symbol(i, Decl(mappedTypeIndexedAccessConstraint.ts, 31, 9)) +>i : Symbol(i, Decl(mappedTypeIndexedAccessConstraint.ts, 37, 9)) }; type SetOptional = Omit & Partial>; ->SetOptional : Symbol(SetOptional, Decl(mappedTypeIndexedAccessConstraint.ts, 33, 2)) ->T : Symbol(T, Decl(mappedTypeIndexedAccessConstraint.ts, 35, 17)) ->K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 35, 19)) ->T : Symbol(T, Decl(mappedTypeIndexedAccessConstraint.ts, 35, 17)) +>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, 35, 17)) ->K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 35, 19)) +>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, 35, 17)) ->K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 35, 19)) +>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, 35, 74)) ->SetOptional : Symbol(SetOptional, Decl(mappedTypeIndexedAccessConstraint.ts, 33, 2)) ->Mappings : Symbol(Mappings, Decl(mappedTypeIndexedAccessConstraint.ts, 26, 39)) +>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, 39, 5)) ->K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 39, 17)) ->PartMappings : Symbol(PartMappings, Decl(mappedTypeIndexedAccessConstraint.ts, 35, 74)) ->o : Symbol(o, Decl(mappedTypeIndexedAccessConstraint.ts, 39, 44)) ->MapperArgs : Symbol(MapperArgs, Decl(mappedTypeIndexedAccessConstraint.ts, 28, 59)) ->K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 39, 17)) ->PartMappings : Symbol(PartMappings, Decl(mappedTypeIndexedAccessConstraint.ts, 35, 74)) ->K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 39, 17)) +>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, 39, 86)) ->v : Symbol(v, Decl(mappedTypeIndexedAccessConstraint.ts, 40, 11)) ->i : Symbol(i, Decl(mappedTypeIndexedAccessConstraint.ts, 40, 14)) +>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, 40, 11)) +>v : Symbol(v, Decl(mappedTypeIndexedAccessConstraint.ts, 46, 11)) >length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) ->i : Symbol(i, Decl(mappedTypeIndexedAccessConstraint.ts, 40, 14)) +>i : Symbol(i, Decl(mappedTypeIndexedAccessConstraint.ts, 46, 14)) "12": ({ v, i }) => Number(v) + i, ->"12" : Symbol("12", Decl(mappedTypeIndexedAccessConstraint.ts, 40, 40)) ->v : Symbol(v, Decl(mappedTypeIndexedAccessConstraint.ts, 41, 12)) ->i : Symbol(i, Decl(mappedTypeIndexedAccessConstraint.ts, 41, 15)) +>"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, 41, 12)) ->i : Symbol(i, Decl(mappedTypeIndexedAccessConstraint.ts, 41, 15)) +>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, 41, 38)) ->v : Symbol(v, Decl(mappedTypeIndexedAccessConstraint.ts, 42, 10)) ->i : Symbol(i, Decl(mappedTypeIndexedAccessConstraint.ts, 42, 13)) ->v : Symbol(v, Decl(mappedTypeIndexedAccessConstraint.ts, 42, 10)) ->i : Symbol(i, Decl(mappedTypeIndexedAccessConstraint.ts, 42, 13)) +>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, 45, 5)) ->K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 45, 24)) ->mapper : Symbol(mapper, Decl(mappedTypeIndexedAccessConstraint.ts, 39, 5)) +>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, 45, 55)) ->K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 45, 24)) ->o : Symbol(o, Decl(mappedTypeIndexedAccessConstraint.ts, 46, 11)) ->MapperArgs : Symbol(MapperArgs, Decl(mappedTypeIndexedAccessConstraint.ts, 28, 59)) ->K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 45, 24)) ->mapper : Symbol(mapper, Decl(mappedTypeIndexedAccessConstraint.ts, 39, 5)) ->key : Symbol(key, Decl(mappedTypeIndexedAccessConstraint.ts, 45, 55)) ->o : Symbol(o, Decl(mappedTypeIndexedAccessConstraint.ts, 46, 11)) +>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, 48, 5)) ->K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 48, 24)) ->mapper : Symbol(mapper, Decl(mappedTypeIndexedAccessConstraint.ts, 39, 5)) +>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, 48, 55)) ->K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 48, 24)) ->o : Symbol(o, Decl(mappedTypeIndexedAccessConstraint.ts, 49, 11)) ->MapperArgs : Symbol(MapperArgs, Decl(mappedTypeIndexedAccessConstraint.ts, 28, 59)) ->K : Symbol(K, Decl(mappedTypeIndexedAccessConstraint.ts, 48, 24)) ->mapper : Symbol(mapper, Decl(mappedTypeIndexedAccessConstraint.ts, 39, 5)) ->key : Symbol(key, Decl(mappedTypeIndexedAccessConstraint.ts, 48, 55)) ->o : Symbol(o, Decl(mappedTypeIndexedAccessConstraint.ts, 49, 11)) +>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 index 2af4e80d4a4ab..f833835d46a95 100644 --- a/tests/baselines/reference/mappedTypeIndexedAccessConstraint.types +++ b/tests/baselines/reference/mappedTypeIndexedAccessConstraint.types @@ -1,6 +1,9 @@ //// [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 @@ -12,10 +15,14 @@ type M1 = { [K in keyof Partial]: M0[K] }; type M2 = { [K in keyof Required]: M1[K] }; >M2 : { a: 1 | undefined; b: 2 | undefined; } -function foo(m1: M1[K], m2: M2[K]) { ->foo : (m1: M1[K], m2: M2[K]) => void +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 @@ -39,6 +46,18 @@ function foo(m1: M1[K], m2: M2[K]) { >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 } diff --git a/tests/cases/compiler/mappedTypeIndexedAccessConstraint.ts b/tests/cases/compiler/mappedTypeIndexedAccessConstraint.ts index c26320e533eba..bf03035870f0b 100644 --- a/tests/cases/compiler/mappedTypeIndexedAccessConstraint.ts +++ b/tests/cases/compiler/mappedTypeIndexedAccessConstraint.ts @@ -1,17 +1,23 @@ // @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] }; -function foo(m1: M1[K], m2: M2[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 From 4cfcd0d088911383c429c70e6a7963c67f39fde1 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 4 Mar 2024 15:23:06 -0800 Subject: [PATCH 5/5] Add comments --- src/compiler/checker.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 08537e69e5f77..0d12ca303980b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14035,6 +14035,8 @@ 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; @@ -14046,6 +14048,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { 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)); }