diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index fae333d4a824c..b2b57b8df765a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13490,12 +13490,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const optionalMask = modifiers & MappedTypeModifiers.IncludeOptional ? 0 : SymbolFlags.Optional; const indexInfos = indexInfo ? [createIndexInfo(stringType, inferReverseMappedType(indexInfo.type, type.mappedType, type.constraintType), readonlyMask && indexInfo.isReadonly)] : emptyArray; const members = createSymbolTable(); + const isSourceInitializerConstAsserted = (type.source.symbol.valueDeclaration?.kind === SyntaxKind.ObjectLiteralExpression || type.source.symbol.valueDeclaration?.kind === SyntaxKind.ArrayLiteralExpression) && isConstAssertion((type.source.symbol.valueDeclaration as ObjectLiteralExpression | ArrayLiteralExpression).parent); for (const prop of getPropertiesOfType(type.source)) { const checkFlags = CheckFlags.ReverseMapped | (readonlyMask && isReadonlySymbol(prop) ? CheckFlags.Readonly : 0); const inferredProp = createSymbol(SymbolFlags.Property | prop.flags & optionalMask, prop.escapedName, checkFlags) as ReverseMappedSymbol; + const propType = getTypeOfSymbol(prop); + const isPropInitializerConstAsserted = prop.declarations?.[0]?.kind === SyntaxKind.PropertyAssignment && isConstAssertion((prop.declarations?.[0] as PropertyAssignment).initializer); inferredProp.declarations = prop.declarations; inferredProp.links.nameType = getSymbolLinks(prop).nameType; - inferredProp.links.propertyType = getTypeOfSymbol(prop); + inferredProp.links.propertyType = !isConstTypeVariable(type.constraintType.type) && !isSourceInitializerConstAsserted && !isPropInitializerConstAsserted ? getBaseTypeOfLiteralType(propType) : propType; if ( type.constraintType.type.flags & TypeFlags.IndexedAccess && (type.constraintType.type as IndexedAccessType).objectType.flags & TypeFlags.TypeParameter diff --git a/tests/cases/compiler/reverseMappedTypesLiteralsInference.ts b/tests/cases/compiler/reverseMappedTypesLiteralsInference.ts new file mode 100644 index 0000000000000..7ea4da2d5243b --- /dev/null +++ b/tests/cases/compiler/reverseMappedTypesLiteralsInference.ts @@ -0,0 +1,176 @@ +interface WithNestedPropd { + boolean?: boolean; + prop?: string; + value?: number; + nested: { + prop: string; + }, + arr: unknown[]; +} + +declare function withNestedPropd(props: { [K in keyof T | keyof WithNestedPropd]: T[K] }): T; + + +// ---------------------------------------- things that now work as they should ---------------------------------------- + +const wnpd_test0 = withNestedPropd({ + // ^? + boolean: false, + prop: 'foo', + value: 10, + nested: { prop: 'bar' }, + extra: 10, + arr: [1,2,3] +}); + +const wnpd_test1 = withNestedPropd({ + // ^? + boolean: false, + prop: 'foo', + value: 10, + nested: { prop: 'bar' } as { prop: 'bar' }, + extra: 10, + arr: [1,2,3] +}); + +const wnpd_test2 = withNestedPropd({ + // ^? + boolean: false, + prop: 'foo', + value: 10, + nested: { prop: 'bar' }, + extra: 10, + arr: [1,2,3] as [1,2,3] +}); + +const wnpd_test3 = withNestedPropd({ + // ^? + boolean: false as const, + prop: 'foo', + value: 10, + nested: { prop: 'bar' }, + extra: 10, + arr: [1,2,3] +}); + +const wnpd_test4 = withNestedPropd({ + // ^? + boolean: false, + prop: 'foo' as const, + value: 10, + nested: { prop: 'bar' }, + extra: 10, + arr: [1,2,3] +}); + +const wnpd_test5 = withNestedPropd({ + // ^? + boolean: false, + prop: 'foo', + value: 10 as const, + nested: { prop: 'bar' }, + extra: 10, + arr: [1,2,3] +}); + +const wnpd_test6 = withNestedPropd({ + // ^? + boolean: false, + prop: 'foo', + value: 10, + nested: { prop: 'bar' } as const, + extra: 10, + arr: [1,2,3] +}); + +const wnpd_test7 = withNestedPropd({ + // ^? + boolean: false, + prop: 'foo', + value: 10, + nested: { prop: 'bar' }, + extra: 10 as const, + arr: [1,2,3] +}); + +const wnpd_test8 = withNestedPropd({ + // ^? + boolean: false, + prop: 'foo', + value: 10, + nested: { prop: 'bar' }, + extra: 10, + arr: [1,2,3] as const +}); + +const wnpd_test9 = withNestedPropd({ + // ^? + boolean: false, + prop: 'foo', + value: 10, + nested: { prop: 'bar' }, + extra: 10, + arr: [1,2,3] +} as const); + +// ---------------------------------------- things that still don't work ---------------------------------------- + +const wnpd_test10 = withNestedPropd({ + // ^? + boolean: false as false, + prop: 'foo', + value: 10, + nested: { prop: 'bar' }, + extra: 10, + arr: [1,2,3] +}); + +const wnpd_test11 = withNestedPropd({ + // ^? + boolean: false, + prop: 'foo' as 'foo', + value: 10, + nested: { prop: 'bar' }, + extra: 10, + arr: [1,2,3] +}); + +const wnpd_test12 = withNestedPropd({ + // ^? + boolean: false, + prop: 'foo', + value: 10 as 10, + nested: { prop: 'bar' }, + extra: 10, + arr: [1,2,3] +}); + +const wnpd_test13 = withNestedPropd({ + // ^? + boolean: false, + prop: 'foo', + value: 10, + nested: { prop: 'bar' }, + extra: 10 as 10, + arr: [1,2,3] +}); + +const wnpd_test14 = withNestedPropd({ + // ^? + boolean: false, + prop: 'foo', + value: 10, + nested: { prop: 'bar' }, + extra: 10, + arr: [1,2,3] +} as { boolean: false, prop: 'foo', value: 10, nested: { prop: 'bar' }, extra: 10, arr: [1,2,3] }); + +// we say goodbye to optional properties +const wnpd_test15 = withNestedPropd({ + // ^? + nested: { prop: 'bar' }, + extra: 10, + arr: [1,2,3] +}); + +// WithNestedPropd has no readonly properties, but if it did we would say goodbye to readonly properties too \ No newline at end of file