diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2bce201e7e35b..71ce6bdbea5c0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13693,24 +13693,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const members = createSymbolTable(); const limitedConstraint = getLimitedConstraint(type); const nameType = getNameTypeFromMappedType(type.mappedType); + const sourceProperties = getPropertiesOfType(type.source); - for (const prop of getPropertiesOfType(type.source)) { - // In case of a reverse mapped type with an intersection constraint or a name type - // we skip those properties that are not assignable to them - // because the extra properties wouldn't get through the application of the mapped type anyway - if (limitedConstraint || nameType) { - const propertyNameType = getLiteralTypeFromProperty(prop, TypeFlags.StringOrNumberLiteralOrUnique); - if (limitedConstraint && !isTypeAssignableTo(propertyNameType, limitedConstraint)) { - continue; - } - if (nameType) { - const nameMapper = appendTypeMapping(type.mappedType.mapper, getTypeParameterFromMappedType(type.mappedType), propertyNameType); - const instantiatedNameType = instantiateType(nameType, nameMapper); - if (instantiatedNameType.flags & TypeFlags.Never) { - continue; - } - } - } + for (const prop of sourceProperties) { const checkFlags = CheckFlags.ReverseMapped | (readonlyMask && isReadonlySymbol(prop) ? CheckFlags.Readonly : 0); const inferredProp = createSymbol(SymbolFlags.Property | prop.flags & optionalMask, prop.escapedName, checkFlags) as ReverseMappedSymbol; inferredProp.declarations = prop.declarations; @@ -13735,6 +13720,39 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } members.set(prop.escapedName, inferredProp); } + + setStructuredTypeMembers(type, members, emptyArray, emptyArray, indexInfos); + + const propsToBeStrippedAway: Set<__String> = new Set(); + + for (const prop of sourceProperties) { + // In case of a reverse mapped type with an intersection constraint or a name type + // we skip those properties that are not assignable to them + // because the extra properties wouldn't get through the application of the mapped type anyway + // + // We do this after having set all the properties because we may need the full reverse-inferred type + // while stripping away some of its properties, it's sort of a circular dependency + // e.g. we need T[K] in { [K in keyof T as T[K] extends string ? K : never ]: { value: T[K] } } + if (limitedConstraint || nameType) { + const propertyNameType = getLiteralTypeFromProperty(prop, TypeFlags.StringOrNumberLiteralOrUnique); + if (limitedConstraint && !isTypeAssignableTo(propertyNameType, limitedConstraint)) { + propsToBeStrippedAway.add(prop.escapedName); + } + if (nameType) { + const nameMapper = appendTypeMapping(type.mappedType.mapper, getTypeParameterFromMappedType(type.mappedType), propertyNameType); + const typeParameterMapper = appendTypeMapping(nameMapper, type.constraintType.type, type); + const instantiatedNameType = instantiateType(nameType, typeParameterMapper); + if (instantiatedNameType.flags & TypeFlags.Never) { + propsToBeStrippedAway.add(prop.escapedName); + } + } + } + } + + for (const propToStripAway of propsToBeStrippedAway) { + members.delete(propToStripAway); + } + setStructuredTypeMembers(type, members, emptyArray, emptyArray, indexInfos); } diff --git a/tests/baselines/reference/reverseMappedTypeInferFromFilteringNameType2.errors.txt b/tests/baselines/reference/reverseMappedTypeInferFromFilteringNameType2.errors.txt index 0cab58fe12be9..d065bde64b6d3 100644 --- a/tests/baselines/reference/reverseMappedTypeInferFromFilteringNameType2.errors.txt +++ b/tests/baselines/reference/reverseMappedTypeInferFromFilteringNameType2.errors.txt @@ -14,9 +14,11 @@ reverseMappedTypeInferFromFilteringNameType2.ts(85,36): error TS2353: Object lit reverseMappedTypeInferFromFilteringNameType2.ts(96,68): error TS2353: Object literal may only specify known properties, and 'extra' does not exist in type '{ prop: "foo"; nested: { prop: string; }; }'. reverseMappedTypeInferFromFilteringNameType2.ts(139,3): error TS2353: Object literal may only specify known properties, and 'extra' does not exist in type '{ readonly types: { actors: { src: "str"; logic: () => Promise; }; }; readonly invoke: { readonly src: "str"; }; }'. reverseMappedTypeInferFromFilteringNameType2.ts(145,3): error TS2353: Object literal may only specify known properties, and 'extra' does not exist in type '{ readonly invoke: { readonly src: "whatever"; }; }'. +reverseMappedTypeInferFromFilteringNameType2.ts(164,11): error TS2353: Object literal may only specify known properties, and 'a' does not exist in type 'Bombolo<{ enabled: false; }>'. +reverseMappedTypeInferFromFilteringNameType2.ts(178,3): error TS2353: Object literal may only specify known properties, and 'not_ok' does not exist in type 'TestRecursiveReferenceToT<{ ok: "it's a string"; }>'. -==== reverseMappedTypeInferFromFilteringNameType2.ts (13 errors) ==== +==== reverseMappedTypeInferFromFilteringNameType2.ts (15 errors) ==== type StateConfig = { entry?: TAction; states?: Record>; @@ -193,4 +195,43 @@ reverseMappedTypeInferFromFilteringNameType2.ts(145,3): error TS2353: Object lit ~~~~~ !!! error TS2353: Object literal may only specify known properties, and 'extra' does not exist in type '{ readonly invoke: { readonly src: "whatever"; }; }'. }); - \ No newline at end of file + + + type Bombolo = { + [ + K in keyof T as K extends 'enabled' + ? K + : 'enabled' extends keyof T + ? T["enabled"] extends true + ? K + : never + : never + ]: T[K] + } + + + declare function bombolo(a: Bombolo): T + + bombolo({ a: "a", b: "b", c: "c", enabled: false as const}) + ~ +!!! error TS2353: Object literal may only specify known properties, and 'a' does not exist in type 'Bombolo<{ enabled: false; }>'. + bombolo({ a: "a", b: "b", c: "c", enabled: true as const}) + + // no excess property check because the parameter type turns out to be the empty object type {} + bombolo({ a: "a", b: "b", c: "c"}) + + type TestRecursiveReferenceToT = { + [K in keyof T as T[K] extends string ? K : never ]: { a: T[K] } + } + + declare function tstrcv(a: TestRecursiveReferenceToT): T + + const res = tstrcv({ + ok: { a: "it's a string" } as const, + not_ok: { a: 123 }, + ~~~~~~ +!!! error TS2353: Object literal may only specify known properties, and 'not_ok' does not exist in type 'TestRecursiveReferenceToT<{ ok: "it's a string"; }>'. + }) + + res + // ^? \ No newline at end of file diff --git a/tests/baselines/reference/reverseMappedTypeInferFromFilteringNameType2.symbols b/tests/baselines/reference/reverseMappedTypeInferFromFilteringNameType2.symbols index 944179ae40452..bec6f3717d6f6 100644 --- a/tests/baselines/reference/reverseMappedTypeInferFromFilteringNameType2.symbols +++ b/tests/baselines/reference/reverseMappedTypeInferFromFilteringNameType2.symbols @@ -489,3 +489,107 @@ const config2 = createXMachine({ }); + +type Bombolo = { +>Bombolo : Symbol(Bombolo, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 145, 3)) +>T : Symbol(T, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 148, 13)) + + [ + K in keyof T as K extends 'enabled' +>K : Symbol(K, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 149, 5)) +>T : Symbol(T, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 148, 13)) +>K : Symbol(K, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 149, 5)) + + ? K +>K : Symbol(K, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 149, 5)) + + : 'enabled' extends keyof T +>T : Symbol(T, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 148, 13)) + + ? T["enabled"] extends true +>T : Symbol(T, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 148, 13)) + + ? K +>K : Symbol(K, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 149, 5)) + + : never + : never + ]: T[K] +>T : Symbol(T, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 148, 13)) +>K : Symbol(K, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 149, 5)) +} + + +declare function bombolo(a: Bombolo): T +>bombolo : Symbol(bombolo, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 158, 1)) +>T : Symbol(T, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 161, 25)) +>a : Symbol(a, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 161, 28)) +>Bombolo : Symbol(Bombolo, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 145, 3)) +>T : Symbol(T, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 161, 25)) +>T : Symbol(T, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 161, 25)) + +bombolo({ a: "a", b: "b", c: "c", enabled: false as const}) +>bombolo : Symbol(bombolo, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 158, 1)) +>a : Symbol(a, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 163, 9)) +>b : Symbol(b, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 163, 17)) +>c : Symbol(c, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 163, 25)) +>enabled : Symbol(enabled, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 163, 33)) +>const : Symbol(const) + +bombolo({ a: "a", b: "b", c: "c", enabled: true as const}) +>bombolo : Symbol(bombolo, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 158, 1)) +>a : Symbol(a, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 164, 9)) +>b : Symbol(b, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 164, 17)) +>c : Symbol(c, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 164, 25)) +>enabled : Symbol(enabled, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 164, 33)) +>const : Symbol(const) + +// no excess property check because the parameter type turns out to be the empty object type {} +bombolo({ a: "a", b: "b", c: "c"}) +>bombolo : Symbol(bombolo, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 158, 1)) +>a : Symbol(a, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 167, 9)) +>b : Symbol(b, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 167, 17)) +>c : Symbol(c, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 167, 25)) + +type TestRecursiveReferenceToT = { +>TestRecursiveReferenceToT : Symbol(TestRecursiveReferenceToT, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 167, 34)) +>T : Symbol(T, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 169, 31)) + + [K in keyof T as T[K] extends string ? K : never ]: { a: T[K] } +>K : Symbol(K, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 170, 5)) +>T : Symbol(T, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 169, 31)) +>T : Symbol(T, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 169, 31)) +>K : Symbol(K, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 170, 5)) +>K : Symbol(K, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 170, 5)) +>a : Symbol(a, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 170, 57)) +>T : Symbol(T, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 169, 31)) +>K : Symbol(K, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 170, 5)) +} + +declare function tstrcv(a: TestRecursiveReferenceToT): T +>tstrcv : Symbol(tstrcv, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 171, 1)) +>T : Symbol(T, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 173, 24)) +>a : Symbol(a, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 173, 27)) +>TestRecursiveReferenceToT : Symbol(TestRecursiveReferenceToT, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 167, 34)) +>T : Symbol(T, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 173, 24)) +>T : Symbol(T, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 173, 24)) + +const res = tstrcv({ +>res : Symbol(res, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 175, 5)) +>tstrcv : Symbol(tstrcv, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 171, 1)) + + ok: { a: "it's a string" } as const, +>ok : Symbol(ok, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 175, 20)) +>a : Symbol(a, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 176, 7)) +>const : Symbol(const) + + not_ok: { a: 123 }, +>not_ok : Symbol(not_ok, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 176, 38)) +>a : Symbol(a, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 177, 11)) + +}) + + res +>res : Symbol(res, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 175, 5)) + +// ^? diff --git a/tests/baselines/reference/reverseMappedTypeInferFromFilteringNameType2.types b/tests/baselines/reference/reverseMappedTypeInferFromFilteringNameType2.types index 6cd5ffdd64f9e..341d331d0633f 100644 --- a/tests/baselines/reference/reverseMappedTypeInferFromFilteringNameType2.types +++ b/tests/baselines/reference/reverseMappedTypeInferFromFilteringNameType2.types @@ -447,3 +447,101 @@ const config2 = createXMachine({ }); + +type Bombolo = { +>Bombolo : Bombolo + + [ + K in keyof T as K extends 'enabled' + ? K + : 'enabled' extends keyof T + ? T["enabled"] extends true +>true : true + + ? K + : never + : never + ]: T[K] +} + + +declare function bombolo(a: Bombolo): T +>bombolo : (a: Bombolo) => T +>a : Bombolo + +bombolo({ a: "a", b: "b", c: "c", enabled: false as const}) +>bombolo({ a: "a", b: "b", c: "c", enabled: false as const}) : { enabled: false; } +>bombolo : (a: Bombolo) => T +>{ a: "a", b: "b", c: "c", enabled: false as const} : { a: string; b: string; c: string; enabled: false; } +>a : string +>"a" : "a" +>b : string +>"b" : "b" +>c : string +>"c" : "c" +>enabled : false +>false as const : false +>false : false + +bombolo({ a: "a", b: "b", c: "c", enabled: true as const}) +>bombolo({ a: "a", b: "b", c: "c", enabled: true as const}) : { a: string; b: string; c: string; enabled: true; } +>bombolo : (a: Bombolo) => T +>{ a: "a", b: "b", c: "c", enabled: true as const} : { a: string; b: string; c: string; enabled: true; } +>a : string +>"a" : "a" +>b : string +>"b" : "b" +>c : string +>"c" : "c" +>enabled : true +>true as const : true +>true : true + +// no excess property check because the parameter type turns out to be the empty object type {} +bombolo({ a: "a", b: "b", c: "c"}) +>bombolo({ a: "a", b: "b", c: "c"}) : {} +>bombolo : (a: Bombolo) => T +>{ a: "a", b: "b", c: "c"} : { a: string; b: string; c: string; } +>a : string +>"a" : "a" +>b : string +>"b" : "b" +>c : string +>"c" : "c" + +type TestRecursiveReferenceToT = { +>TestRecursiveReferenceToT : TestRecursiveReferenceToT + + [K in keyof T as T[K] extends string ? K : never ]: { a: T[K] } +>a : T[K] +} + +declare function tstrcv(a: TestRecursiveReferenceToT): T +>tstrcv : (a: TestRecursiveReferenceToT) => T +>a : TestRecursiveReferenceToT + +const res = tstrcv({ +>res : { ok: "it's a string"; } +>tstrcv({ ok: { a: "it's a string" } as const, not_ok: { a: 123 },}) : { ok: "it's a string"; } +>tstrcv : (a: TestRecursiveReferenceToT) => T +>{ ok: { a: "it's a string" } as const, not_ok: { a: 123 },} : { ok: { readonly a: "it's a string"; }; not_ok: { a: number; }; } + + ok: { a: "it's a string" } as const, +>ok : { readonly a: "it's a string"; } +>{ a: "it's a string" } as const : { readonly a: "it's a string"; } +>{ a: "it's a string" } : { readonly a: "it's a string"; } +>a : "it's a string" +>"it's a string" : "it's a string" + + not_ok: { a: 123 }, +>not_ok : { a: number; } +>{ a: 123 } : { a: number; } +>a : number +>123 : 123 + +}) + + res +>res : { ok: "it's a string"; } + +// ^? diff --git a/tests/cases/compiler/reverseMappedTypeInferFromFilteringNameType2.ts b/tests/cases/compiler/reverseMappedTypeInferFromFilteringNameType2.ts index 737050c533a26..aa5a9d15596e8 100644 --- a/tests/cases/compiler/reverseMappedTypeInferFromFilteringNameType2.ts +++ b/tests/cases/compiler/reverseMappedTypeInferFromFilteringNameType2.ts @@ -148,3 +148,39 @@ const config2 = createXMachine({ }, extra: 10, }); + + +type Bombolo = { + [ + K in keyof T as K extends 'enabled' + ? K + : 'enabled' extends keyof T + ? T["enabled"] extends true + ? K + : never + : never + ]: T[K] +} + + +declare function bombolo(a: Bombolo): T + +bombolo({ a: "a", b: "b", c: "c", enabled: false as const}) +bombolo({ a: "a", b: "b", c: "c", enabled: true as const}) + +// no excess property check because the parameter type turns out to be the empty object type {} +bombolo({ a: "a", b: "b", c: "c"}) + +type TestRecursiveReferenceToT = { + [K in keyof T as T[K] extends string ? K : never ]: { a: T[K] } +} + +declare function tstrcv(a: TestRecursiveReferenceToT): T + +const res = tstrcv({ + ok: { a: "it's a string" } as const, + not_ok: { a: 123 }, +}) + + res +// ^? \ No newline at end of file