@@ -2038,6 +2038,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
20382038 var unknownEmptyObjectType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray);
20392039 var unknownUnionType = strictNullChecks ? getUnionType([undefinedType, nullType, unknownEmptyObjectType]) : unknownType;
20402040
2041+ var keyofConstraintObjectType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, [stringType, numberType, esSymbolType].map(t => createIndexInfo(t, unknownType, /*isReadonly*/ false))); // { [k: string | number | symbol]: unknown; }
2042+
20412043 var emptyGenericType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray) as ObjectType as GenericType;
20422044 emptyGenericType.instantiations = new Map<string, TypeReference>();
20432045
@@ -13667,21 +13669,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1366713669 return instantiateType(instantiable, createTypeMapper([type.indexType, type.objectType], [getNumberLiteralType(0), createTupleType([replacement])]));
1366813670 }
1366913671
13670- // If the original mapped type had an intersection constraint we extract its components,
13671- // and we make an attempt to do so even if the intersection has been reduced to a union.
13672- // This entire process allows us to possibly retrieve the filtering type literals.
13673- // e.g. { [K in keyof U & ("a" | "b") ] } -> "a" | "b"
13674- function getLimitedConstraint(type: ReverseMappedType) {
13672+ // If the original mapped type had an union/intersection constraint
13673+ // there is a chance that it includes an intersection that could limit what members are allowed
13674+ function getReverseMappedTypeMembersLimitingConstraint(type: ReverseMappedType) {
1367513675 const constraint = getConstraintTypeFromMappedType(type.mappedType);
13676- if (!(constraint.flags & TypeFlags.Union || constraint.flags & TypeFlags.Intersection)) {
13677- return;
13678- }
13679- const origin = (constraint.flags & TypeFlags.Union) ? (constraint as UnionType).origin : (constraint as IntersectionType);
13680- if (!origin || !(origin.flags & TypeFlags.Intersection)) {
13676+ if (constraint === type.constraintType) {
1368113677 return;
1368213678 }
13683- const limitedConstraint = getIntersectionType((origin as IntersectionType).types.filter(t => t !== type.constraintType) );
13684- return limitedConstraint !== neverType ? limitedConstraint : undefined ;
13679+ const mapper = appendTypeMapping(type.mappedType.mapper, type.constraintType.type, keyofConstraintObjectType );
13680+ return getBaseConstraintOrType(instantiateType(constraint, mapper)) ;
1368513681 }
1368613682
1368713683 function resolveReverseMappedTypeMembers(type: ReverseMappedType) {
@@ -13691,14 +13687,21 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1369113687 const optionalMask = modifiers & MappedTypeModifiers.IncludeOptional ? 0 : SymbolFlags.Optional;
1369213688 const indexInfos = indexInfo ? [createIndexInfo(stringType, inferReverseMappedType(indexInfo.type, type.mappedType, type.constraintType), readonlyMask && indexInfo.isReadonly)] : emptyArray;
1369313689 const members = createSymbolTable();
13694- const limitedConstraint = getLimitedConstraint (type);
13690+ const membersLimitingConstraint = getReverseMappedTypeMembersLimitingConstraint (type);
1369513691 for (const prop of getPropertiesOfType(type.source)) {
13696- // In case of a reverse mapped type with an intersection constraint, if we were able to
13697- // extract the filtering type literals we skip those properties that are not assignable to them,
13698- // because the extra properties wouldn't get through the application of the mapped type anyway
13699- if (limitedConstraint) {
13692+ // we skip those properties that are not assignable to the limiting constraint
13693+ // the extra properties wouldn't get through the application of the mapped type anyway
13694+ // and their inferred type might not satisfy the type parameter's constraint
13695+ // which, in turn, could fail the check if the inferred type is assignable to its constraint
13696+ //
13697+ // inferring `{ a: number; b: string }` wouldn't satisfy T's constraint so b has to be skipped here
13698+ //
13699+ // declare function fn<T extends Record<string, number>>(arg: { [K in keyof T & "a"]: T[K] }): T
13700+ // const obj = { a: 1, b: '2' };
13701+ // fn(obj);
13702+ if (membersLimitingConstraint) {
1370013703 const propertyNameType = getLiteralTypeFromProperty(prop, TypeFlags.StringOrNumberLiteralOrUnique);
13701- if (!isTypeAssignableTo(propertyNameType, limitedConstraint )) {
13704+ if (!isTypeAssignableTo(propertyNameType, membersLimitingConstraint )) {
1370213705 continue;
1370313706 }
1370413707 }
@@ -25749,9 +25752,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2574925752 }
2575025753
2575125754 function inferToMappedType(source: Type, target: MappedType, constraintType: Type): boolean {
25752- if (( constraintType.flags & TypeFlags.Union) || (constraintType.flags & TypeFlags.Intersection) ) {
25755+ if (constraintType.flags & TypeFlags.UnionOrIntersection ) {
2575325756 let result = false;
25754- for (const type of (constraintType as (UnionType | IntersectionType) ).types) {
25757+ for (const type of (constraintType as UnionOrIntersectionType ).types) {
2575525758 result = inferToMappedType(source, target, type) || result;
2575625759 }
2575725760 return result;
0 commit comments