@@ -2038,6 +2038,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2038
2038
var unknownEmptyObjectType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray);
2039
2039
var unknownUnionType = strictNullChecks ? getUnionType([undefinedType, nullType, unknownEmptyObjectType]) : unknownType;
2040
2040
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
+
2041
2043
var emptyGenericType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray) as ObjectType as GenericType;
2042
2044
emptyGenericType.instantiations = new Map<string, TypeReference>();
2043
2045
@@ -13667,21 +13669,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
13667
13669
return instantiateType(instantiable, createTypeMapper([type.indexType, type.objectType], [getNumberLiteralType(0), createTupleType([replacement])]));
13668
13670
}
13669
13671
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) {
13675
13675
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) {
13681
13677
return;
13682
13678
}
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)) ;
13685
13681
}
13686
13682
13687
13683
function resolveReverseMappedTypeMembers(type: ReverseMappedType) {
@@ -13691,14 +13687,21 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
13691
13687
const optionalMask = modifiers & MappedTypeModifiers.IncludeOptional ? 0 : SymbolFlags.Optional;
13692
13688
const indexInfos = indexInfo ? [createIndexInfo(stringType, inferReverseMappedType(indexInfo.type, type.mappedType, type.constraintType), readonlyMask && indexInfo.isReadonly)] : emptyArray;
13693
13689
const members = createSymbolTable();
13694
- const limitedConstraint = getLimitedConstraint (type);
13690
+ const membersLimitingConstraint = getReverseMappedTypeMembersLimitingConstraint (type);
13695
13691
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) {
13700
13703
const propertyNameType = getLiteralTypeFromProperty(prop, TypeFlags.StringOrNumberLiteralOrUnique);
13701
- if (!isTypeAssignableTo(propertyNameType, limitedConstraint )) {
13704
+ if (!isTypeAssignableTo(propertyNameType, membersLimitingConstraint )) {
13702
13705
continue;
13703
13706
}
13704
13707
}
@@ -25749,9 +25752,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
25749
25752
}
25750
25753
25751
25754
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 ) {
25753
25756
let result = false;
25754
- for (const type of (constraintType as (UnionType | IntersectionType) ).types) {
25757
+ for (const type of (constraintType as UnionOrIntersectionType ).types) {
25755
25758
result = inferToMappedType(source, target, type) || result;
25756
25759
}
25757
25760
return result;
0 commit comments